Skip to content

Commit e6457a4

Browse files
authored
fix: always sort JSON encoded keys (#318)
* fix: always sort JSON encoded keys * Enable more tests on Linux and Windows * more query tests * more tests * disable tests for linux and windows * Update CHANGELOG.md
1 parent b2a0d9c commit e6457a4

21 files changed

+124
-175
lines changed

CHANGELOG.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,15 @@
22

33
### main
44

5-
[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/3.1.0...main)
5+
[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/3.1.1...main)
66
* _Contributing to this repo? Add info about your change here to be included in the next release_
77

8+
### 3.1.1
9+
[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/3.1.0...3.1.1)
10+
11+
__Fixes__
12+
- Always sort keys when using the ParseEncoder as it can cause issues when trying to save ParseObject's that have children ([#318](https://github.com/parse-community/Parse-Swift/pull/318)), thanks to [Corey Baker](https://github.com/cbaker6).
13+
814
### 3.1.0
915
[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/3.0.0...3.1.0)
1016

Sources/ParseSwift/Coding/ParseCoding.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ extension ParseCoding {
2020
static func jsonEncoder() -> JSONEncoder {
2121
let encoder = JSONEncoder()
2222
encoder.dateEncodingStrategy = parseDateEncodingStrategy
23+
encoder.outputFormatting = .sortedKeys
2324
return encoder
2425
}
2526

Sources/ParseSwift/Coding/ParseEncoder.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ public struct ParseEncoder {
9595
let encoder = _ParseEncoder(codingPath: [], dictionary: NSMutableDictionary(), skippingKeys: SkipKeys.none.keys())
9696
if let dateEncodingStrategy = dateEncodingStrategy {
9797
encoder.dateEncodingStrategy = dateEncodingStrategy
98+
encoder.outputFormatting = .sortedKeys
9899
}
99100
return try encoder.encodeObject(value,
100101
collectChildren: false,
@@ -114,6 +115,7 @@ public struct ParseEncoder {
114115
if let dateEncodingStrategy = dateEncodingStrategy {
115116
encoder.dateEncodingStrategy = dateEncodingStrategy
116117
}
118+
encoder.outputFormatting = .sortedKeys
117119
return try encoder.encodeObject(value,
118120
collectChildren: false,
119121
uniquePointer: nil,
@@ -135,6 +137,7 @@ public struct ParseEncoder {
135137
if let dateEncodingStrategy = dateEncodingStrategy {
136138
encoder.dateEncodingStrategy = dateEncodingStrategy
137139
}
140+
encoder.outputFormatting = .sortedKeys
138141
return try encoder.encodeObject(value,
139142
collectChildren: true,
140143
uniquePointer: try? value.toPointer(),
@@ -157,6 +160,7 @@ public struct ParseEncoder {
157160
if let dateEncodingStrategy = dateEncodingStrategy {
158161
encoder.dateEncodingStrategy = dateEncodingStrategy
159162
}
163+
encoder.outputFormatting = .sortedKeys
160164
return try encoder.encodeObject(value,
161165
collectChildren: collectChildren,
162166
uniquePointer: nil,

Sources/ParseSwift/ParseConstants.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import Foundation
1010

1111
enum ParseConstants {
1212
static let sdk = "swift"
13-
static let version = "3.1.0"
13+
static let version = "3.1.1"
1414
static let fileManagementDirectory = "parse/"
1515
static let fileManagementPrivateDocumentsDirectory = "Private Documents/"
1616
static let fileManagementLibraryDirectory = "Library/"

Tests/ParseSwiftTests/IOS13Tests.swift

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,6 @@ class IOS13Tests: XCTestCase { // swiftlint:disable:this type_body_length
9090
wait(for: [expectation2], timeout: 20.0)
9191
}
9292

93-
#if !os(Linux) && !os(Android) && !os(Windows)
9493
func testSaveCommand() throws {
9594
let score = GameScore(points: 10)
9695
let className = score.className
@@ -101,7 +100,7 @@ class IOS13Tests: XCTestCase { // swiftlint:disable:this type_body_length
101100
XCTAssertEqual(command.method, API.Method.POST)
102101
XCTAssertNil(command.params)
103102

104-
let expected = "GameScore ({\"points\":10,\"player\":\"Jen\"})"
103+
let expected = "GameScore ({\"player\":\"Jen\",\"points\":10})"
105104
let decoded = score.debugDescription
106105
XCTAssertEqual(decoded, expected)
107106
}
@@ -125,13 +124,12 @@ class IOS13Tests: XCTestCase { // swiftlint:disable:this type_body_length
125124
return
126125
}
127126

128-
let expected = "{\"points\":10,\"player\":\"Jen\"}"
127+
let expected = "{\"player\":\"Jen\",\"points\":10}"
129128
let encoded = try ParseCoding.parseEncoder()
130129
.encode(body, collectChildren: false,
131130
objectsSavedBeforeThisOne: nil,
132131
filesSavedBeforeThisOne: nil).encoded
133132
let decoded = try XCTUnwrap(String(data: encoded, encoding: .utf8))
134133
XCTAssertEqual(decoded, expected)
135134
}
136-
#endif
137135
}

Tests/ParseSwiftTests/ParseBytesTests.swift

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ class ParseBytesTests: XCTestCase {
3939
XCTAssertEqual(decoded, bytes)
4040
}
4141

42-
#if !os(Linux) && !os(Android) && !os(Windows)
4342
func testDebugString() {
4443
let bytes = ParseBytes(base64: "ZnJveW8=")
4544
let expected = "ParseBytes ({\"__type\":\"Bytes\",\"base64\":\"ZnJveW8=\"})"
@@ -63,5 +62,4 @@ class ParseBytesTests: XCTestCase {
6362
let bytes2 = ParseBytes(data: data)
6463
XCTAssertEqual(bytes2.description, expected)
6564
}
66-
#endif
6765
}

Tests/ParseSwiftTests/ParseCloudTests.swift

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,6 @@ class ParseCloudTests: XCTestCase { // swiftlint:disable:this type_body_length
9999
XCTAssertEqual(decoded, expected, "\"functionJobName\" key should be skipped by ParseEncoder")
100100
}
101101

102-
#if !os(Linux) && !os(Android) && !os(Windows)
103102
func testDebugString() {
104103
let cloud = Cloud2(functionJobName: "test", customKey: "parse")
105104
let expected = "{\"customKey\":\"parse\",\"functionJobName\":\"test\"}"
@@ -111,7 +110,6 @@ class ParseCloudTests: XCTestCase { // swiftlint:disable:this type_body_length
111110
let expected = "{\"customKey\":\"parse\",\"functionJobName\":\"test\"}"
112111
XCTAssertEqual(cloud.description, expected)
113112
}
114-
#endif
115113

116114
func testCallFunctionCommand() throws {
117115
let cloud = Cloud(functionJobName: "test")

Tests/ParseSwiftTests/ParseConfigTests.swift

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,6 @@ class ParseConfigTests: XCTestCase { // swiftlint:disable:this type_body_length
146146
XCTAssertNil(command.body)
147147
}
148148

149-
#if !os(Linux) && !os(Android) && !os(Windows)
150149
func testDebugString() {
151150
var config = Config()
152151
config.welcomeMessage = "Hello"
@@ -160,7 +159,6 @@ class ParseConfigTests: XCTestCase { // swiftlint:disable:this type_body_length
160159
let expected = "{\"welcomeMessage\":\"Hello\"}"
161160
XCTAssertEqual(config.description, expected)
162161
}
163-
#endif
164162

165163
func testFetch() {
166164
userLogin()

Tests/ParseSwiftTests/ParseEncoderTests/TestParseEncoder.swift

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -62,14 +62,12 @@ class TestParseEncoder: XCTestCase {
6262
_testRoundTrip(of: address)
6363
}
6464

65-
#if !os(Linux) && !os(Android) && !os(Windows)
6665
func testEncodingTopLevelStructuredClass() {
6766
// Person is a class with multiple fields.
68-
let expectedJSON = "{\"name\":\"Johnny Appleseed\",\"email\":\"[email protected]\"}".data(using: .utf8)!
67+
let expectedJSON = "{\"email\":\"[email protected]\",\"name\":\"Johnny Appleseed\"}".data(using: .utf8)!
6968
let person = Person.testValue
7069
_testRoundTrip(of: person, expectedJSON: expectedJSON)
7170
}
72-
#endif
7371

7472
func testEncodingTopLevelStructuredSingleStruct() {
7573
// Numbers is a struct which encodes as an array through a single value container.
@@ -102,7 +100,7 @@ class TestParseEncoder: XCTestCase {
102100
_testRoundTrip(of: EnhancedBool.fileNotFound, expectedJSON: "null".data(using: .utf8)!)
103101
}
104102

105-
#if !os(Linux) && !os(Android) && !os(Windows)
103+
#if !os(Linux) && !os(Android) && !os(Windows)
106104
func testEncodingMultipleNestedContainersWithTheSameTopLevelKey() {
107105
struct Model: Codable, Equatable {
108106
let first: String
@@ -159,7 +157,6 @@ class TestParseEncoder: XCTestCase {
159157
}
160158
}
161159
#endif
162-
163160
/*
164161
func testEncodingConflictedTypeNestedContainersWithTheSameTopLevelKey() throws {
165162
struct Model: Encodable, Equatable {
@@ -202,13 +199,11 @@ class TestParseEncoder: XCTestCase {
202199
}*/
203200

204201
// MARK: - Output Formatting Tests
205-
#if !os(Linux) && !os(Android) && !os(Windows)
206202
func testEncodingOutputFormattingDefault() {
207-
let expectedJSON = "{\"name\":\"Johnny Appleseed\",\"email\":\"[email protected]\"}".data(using: .utf8)!
203+
let expectedJSON = "{\"email\":\"[email protected]\",\"name\":\"Johnny Appleseed\"}".data(using: .utf8)!
208204
let person = Person.testValue
209205
_testRoundTrip(of: person, expectedJSON: expectedJSON)
210206
}
211-
#endif
212207
/*
213208
func testEncodingOutputFormattingPrettyPrinted() {
214209
let expectedJSON = "{\n \"name\" : \"Johnny Appleseed\",\n \"email\" : \"[email protected]\"\n}".data(using: .utf8)!

Tests/ParseSwiftTests/ParseFileTests.swift

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,6 @@ class ParseFileTests: XCTestCase { // swiftlint:disable:this type_body_length
193193
XCTAssertEqual(parseFile1, parseFile2, "no urls, but localIds shoud be the same")
194194
}
195195

196-
#if !os(Linux) && !os(Android) && !os(Windows)
197196
func testDebugString() throws {
198197
guard let sampleData = "Hello World".data(using: .utf8) else {
199198
throw ParseError(code: .unknownError, message: "Should have converted to data")
@@ -203,11 +202,10 @@ class ParseFileTests: XCTestCase { // swiftlint:disable:this type_body_length
203202
metadata: ["Testing": "123"],
204203
tags: ["Hey": "now"])
205204
XCTAssertEqual(parseFile.debugDescription,
206-
"ParseFile ({\"name\":\"sampleData.txt\",\"__type\":\"File\"})")
205+
"ParseFile ({\"__type\":\"File\",\"name\":\"sampleData.txt\"})")
207206
XCTAssertEqual(parseFile.description,
208-
"ParseFile ({\"name\":\"sampleData.txt\",\"__type\":\"File\"})")
207+
"ParseFile ({\"__type\":\"File\",\"name\":\"sampleData.txt\"})")
209208
}
210-
#endif
211209

212210
func testSave() throws {
213211
guard let sampleData = "Hello World".data(using: .utf8) else {

Tests/ParseSwiftTests/ParseGeoPointTests.swift

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -88,19 +88,17 @@ class ParseGeoPointTests: XCTestCase {
8888
}
8989
}
9090

91-
#if !os(Linux) && !os(Android) && !os(Windows)
9291
func testDebugString() throws {
9392
let point = try ParseGeoPoint(latitude: 10, longitude: 20)
94-
let expected = "ParseGeoPoint ({\"__type\":\"GeoPoint\",\"longitude\":20,\"latitude\":10})"
93+
let expected = "ParseGeoPoint ({\"__type\":\"GeoPoint\",\"latitude\":10,\"longitude\":20})"
9594
XCTAssertEqual(point.debugDescription, expected)
9695
}
9796

9897
func testDescription() throws {
9998
let point = try ParseGeoPoint(latitude: 10, longitude: 20)
100-
let expected = "ParseGeoPoint ({\"__type\":\"GeoPoint\",\"longitude\":20,\"latitude\":10})"
99+
let expected = "ParseGeoPoint ({\"__type\":\"GeoPoint\",\"latitude\":10,\"longitude\":20})"
101100
XCTAssertEqual(point.description, expected)
102101
}
103-
#endif
104102

105103
// swiftlint:disable:next function_body_length
106104
func testGeoUtilityDistance() throws {

Tests/ParseSwiftTests/ParseLiveQueryTests.swift

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ class ParseLiveQueryTests: XCTestCase {
196196
return
197197
}
198198
// swiftlint:disable:next line_length
199-
let expected = "{\"op\":\"connect\",\"applicationId\":\"applicationId\",\"clientKey\":\"clientKey\",\"masterKey\":\"masterKey\",\"installationId\":\"\(installationId)\"}"
199+
let expected = "{\"applicationId\":\"applicationId\",\"clientKey\":\"clientKey\",\"installationId\":\"\(installationId)\",\"masterKey\":\"masterKey\",\"op\":\"connect\"}"
200200
let message = StandardMessage(operation: .connect, additionalProperties: true)
201201
let encoded = try ParseCoding.jsonEncoder()
202202
.encode(message)
@@ -206,7 +206,7 @@ class ParseLiveQueryTests: XCTestCase {
206206

207207
func testSubscribeMessageEncoding() throws {
208208
// swiftlint:disable:next line_length
209-
let expected = "{\"op\":\"subscribe\",\"requestId\":1,\"query\":{\"className\":\"GameScore\",\"where\":{\"points\":{\"$gt\":9}},\"fields\":[\"points\"]}}"
209+
let expected = "{\"op\":\"subscribe\",\"query\":{\"className\":\"GameScore\",\"fields\":[\"points\"],\"where\":{\"points\":{\"$gt\":9}}},\"requestId\":1}"
210210
let query = GameScore.query("points" > 9)
211211
.fields(["points"])
212212
let message = SubscribeMessage(operation: .subscribe,
@@ -259,7 +259,7 @@ class ParseLiveQueryTests: XCTestCase {
259259
}
260260

261261
func testConnectionResponseDecoding() throws {
262-
let expected = "{\"op\":\"connected\",\"clientId\":\"yolo\",\"installationId\":\"naw\"}"
262+
let expected = "{\"clientId\":\"yolo\",\"installationId\":\"naw\",\"op\":\"connected\"}"
263263
let message = ConnectionResponse(op: .connected, clientId: "yolo", installationId: "naw")
264264
let encoded = try ParseCoding.jsonEncoder()
265265
.encode(message)
@@ -268,7 +268,7 @@ class ParseLiveQueryTests: XCTestCase {
268268
}
269269

270270
func testUnsubscribeResponseDecoding() throws {
271-
let expected = "{\"op\":\"connected\",\"clientId\":\"yolo\",\"requestId\":1,\"installationId\":\"naw\"}"
271+
let expected = "{\"clientId\":\"yolo\",\"installationId\":\"naw\",\"op\":\"connected\",\"requestId\":1}"
272272
let message = UnsubscribedResponse(op: .connected, requestId: 1, clientId: "yolo", installationId: "naw")
273273
let encoded = try ParseCoding.jsonEncoder()
274274
.encode(message)
@@ -278,7 +278,7 @@ class ParseLiveQueryTests: XCTestCase {
278278

279279
func testEventResponseDecoding() throws {
280280
// swiftlint:disable:next line_length
281-
let expected = "{\"op\":\"connected\",\"object\":{\"points\":10},\"requestId\":1,\"clientId\":\"yolo\",\"installationId\":\"naw\"}"
281+
let expected = "{\"clientId\":\"yolo\",\"installationId\":\"naw\",\"object\":{\"points\":10},\"op\":\"connected\",\"requestId\":1}"
282282
let score = GameScore(points: 10)
283283
let message = EventResponse(op: .connected,
284284
requestId: 1,
@@ -292,7 +292,7 @@ class ParseLiveQueryTests: XCTestCase {
292292
}
293293

294294
func testErrorResponseDecoding() throws {
295-
let expected = "{\"code\":1,\"op\":\"error\",\"error\":\"message\",\"reconnect\":true}"
295+
let expected = "{\"code\":1,\"error\":\"message\",\"op\":\"error\",\"reconnect\":true}"
296296
let message = ErrorResponse(op: .error, code: 1, message: "message", reconnect: true)
297297
let encoded = try ParseCoding.jsonEncoder()
298298
.encode(message)
@@ -301,7 +301,7 @@ class ParseLiveQueryTests: XCTestCase {
301301
}
302302

303303
func testPreliminaryResponseDecoding() throws {
304-
let expected = "{\"op\":\"subscribed\",\"clientId\":\"message\",\"requestId\":1,\"installationId\":\"naw\"}"
304+
let expected = "{\"clientId\":\"message\",\"installationId\":\"naw\",\"op\":\"subscribed\",\"requestId\":1}"
305305
let message = PreliminaryMessageResponse(op: .subscribed,
306306
requestId: 1,
307307
clientId: "message",

Tests/ParseSwiftTests/ParseObjectBatchTests.swift

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,6 @@ class ParseObjectBatchTests: XCTestCase { // swiftlint:disable:this type_body_le
7272
try ParseStorage.shared.deleteAll()
7373
}
7474

75-
//COREY: Linux decodes this differently for some reason
76-
#if !os(Linux) && !os(Android) && !os(Windows)
7775
func testSaveAllCommand() throws {
7876
let score = GameScore(points: 10)
7977
let score2 = GameScore(points: 20)
@@ -92,15 +90,14 @@ class ParseObjectBatchTests: XCTestCase { // swiftlint:disable:this type_body_le
9290
let commands = try objects.map { try $0.saveCommand() }
9391
let body = BatchCommand(requests: commands, transaction: false)
9492
// swiftlint:disable:next line_length
95-
let expected = "{\"requests\":[{\"path\":\"\\/classes\\/GameScore\",\"method\":\"POST\",\"body\":{\"points\":10}},{\"path\":\"\\/classes\\/GameScore\",\"method\":\"POST\",\"body\":{\"points\":20}}],\"transaction\":false}"
93+
let expected = "{\"requests\":[{\"body\":{\"points\":10},\"method\":\"POST\",\"path\":\"\\/classes\\/GameScore\"},{\"body\":{\"points\":20},\"method\":\"POST\",\"path\":\"\\/classes\\/GameScore\"}],\"transaction\":false}"
9694
let encoded = try ParseCoding.parseEncoder()
9795
.encode(body, collectChildren: false,
9896
objectsSavedBeforeThisOne: nil,
9997
filesSavedBeforeThisOne: nil).encoded
10098
let decoded = try XCTUnwrap(String(data: encoded, encoding: .utf8))
10199
XCTAssertEqual(decoded, expected)
102100
}
103-
#endif
104101

105102
func testSaveAll() { // swiftlint:disable:this function_body_length cyclomatic_complexity
106103
let score = GameScore(points: 10)
@@ -371,7 +368,6 @@ class ParseObjectBatchTests: XCTestCase { // swiftlint:disable:this type_body_le
371368
}
372369
}
373370

374-
#if !os(Linux) && !os(Android) && !os(Windows)
375371
func testUpdateAllCommand() throws {
376372
var score = GameScore(points: 10)
377373
var score2 = GameScore(points: 20)
@@ -395,7 +391,7 @@ class ParseObjectBatchTests: XCTestCase { // swiftlint:disable:this type_body_le
395391
}
396392
let body = BatchCommand(requests: commands, transaction: false)
397393
// swiftlint:disable:next line_length
398-
let expected = "{\"requests\":[{\"path\":\"\\/1\\/classes\\/GameScore\\/yarr\",\"method\":\"PUT\",\"body\":{\"points\":10}},{\"path\":\"\\/1\\/classes\\/GameScore\\/yolo\",\"method\":\"PUT\",\"body\":{\"points\":20}}],\"transaction\":false}"
394+
let expected = "{\"requests\":[{\"body\":{\"points\":10},\"method\":\"PUT\",\"path\":\"\\/1\\/classes\\/GameScore\\/yarr\"},{\"body\":{\"points\":20},\"method\":\"PUT\",\"path\":\"\\/1\\/classes\\/GameScore\\/yolo\"}],\"transaction\":false}"
399395

400396
let encoded = try ParseCoding.parseEncoder()
401397
.encode(body, collectChildren: false,
@@ -404,7 +400,6 @@ class ParseObjectBatchTests: XCTestCase { // swiftlint:disable:this type_body_le
404400
let decoded = try XCTUnwrap(String(data: encoded, encoding: .utf8))
405401
XCTAssertEqual(decoded, expected)
406402
}
407-
#endif
408403

409404
func testUpdateAll() { // swiftlint:disable:this function_body_length cyclomatic_complexity
410405
var score = GameScore(points: 10)

0 commit comments

Comments
 (0)