Skip to content

Commit

Permalink
fix: always sort JSON encoded keys (#318)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
cbaker6 authored Jan 16, 2022
1 parent b2a0d9c commit e6457a4
Show file tree
Hide file tree
Showing 21 changed files with 124 additions and 175 deletions.
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,15 @@

### main

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

### 3.1.1
[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/3.1.0...3.1.1)

__Fixes__
- 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).

### 3.1.0
[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/3.0.0...3.1.0)

Expand Down
1 change: 1 addition & 0 deletions Sources/ParseSwift/Coding/ParseCoding.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ extension ParseCoding {
static func jsonEncoder() -> JSONEncoder {
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = parseDateEncodingStrategy
encoder.outputFormatting = .sortedKeys
return encoder
}

Expand Down
4 changes: 4 additions & 0 deletions Sources/ParseSwift/Coding/ParseEncoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ public struct ParseEncoder {
let encoder = _ParseEncoder(codingPath: [], dictionary: NSMutableDictionary(), skippingKeys: SkipKeys.none.keys())
if let dateEncodingStrategy = dateEncodingStrategy {
encoder.dateEncodingStrategy = dateEncodingStrategy
encoder.outputFormatting = .sortedKeys
}
return try encoder.encodeObject(value,
collectChildren: false,
Expand All @@ -114,6 +115,7 @@ public struct ParseEncoder {
if let dateEncodingStrategy = dateEncodingStrategy {
encoder.dateEncodingStrategy = dateEncodingStrategy
}
encoder.outputFormatting = .sortedKeys
return try encoder.encodeObject(value,
collectChildren: false,
uniquePointer: nil,
Expand All @@ -135,6 +137,7 @@ public struct ParseEncoder {
if let dateEncodingStrategy = dateEncodingStrategy {
encoder.dateEncodingStrategy = dateEncodingStrategy
}
encoder.outputFormatting = .sortedKeys
return try encoder.encodeObject(value,
collectChildren: true,
uniquePointer: try? value.toPointer(),
Expand All @@ -157,6 +160,7 @@ public struct ParseEncoder {
if let dateEncodingStrategy = dateEncodingStrategy {
encoder.dateEncodingStrategy = dateEncodingStrategy
}
encoder.outputFormatting = .sortedKeys
return try encoder.encodeObject(value,
collectChildren: collectChildren,
uniquePointer: nil,
Expand Down
2 changes: 1 addition & 1 deletion Sources/ParseSwift/ParseConstants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import Foundation

enum ParseConstants {
static let sdk = "swift"
static let version = "3.1.0"
static let version = "3.1.1"
static let fileManagementDirectory = "parse/"
static let fileManagementPrivateDocumentsDirectory = "Private Documents/"
static let fileManagementLibraryDirectory = "Library/"
Expand Down
6 changes: 2 additions & 4 deletions Tests/ParseSwiftTests/IOS13Tests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,6 @@ class IOS13Tests: XCTestCase { // swiftlint:disable:this type_body_length
wait(for: [expectation2], timeout: 20.0)
}

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

let expected = "GameScore ({\"points\":10,\"player\":\"Jen\"})"
let expected = "GameScore ({\"player\":\"Jen\",\"points\":10})"
let decoded = score.debugDescription
XCTAssertEqual(decoded, expected)
}
Expand All @@ -125,13 +124,12 @@ class IOS13Tests: XCTestCase { // swiftlint:disable:this type_body_length
return
}

let expected = "{\"points\":10,\"player\":\"Jen\"}"
let expected = "{\"player\":\"Jen\",\"points\":10}"
let encoded = try ParseCoding.parseEncoder()
.encode(body, collectChildren: false,
objectsSavedBeforeThisOne: nil,
filesSavedBeforeThisOne: nil).encoded
let decoded = try XCTUnwrap(String(data: encoded, encoding: .utf8))
XCTAssertEqual(decoded, expected)
}
#endif
}
2 changes: 0 additions & 2 deletions Tests/ParseSwiftTests/ParseBytesTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ class ParseBytesTests: XCTestCase {
XCTAssertEqual(decoded, bytes)
}

#if !os(Linux) && !os(Android) && !os(Windows)
func testDebugString() {
let bytes = ParseBytes(base64: "ZnJveW8=")
let expected = "ParseBytes ({\"__type\":\"Bytes\",\"base64\":\"ZnJveW8=\"})"
Expand All @@ -63,5 +62,4 @@ class ParseBytesTests: XCTestCase {
let bytes2 = ParseBytes(data: data)
XCTAssertEqual(bytes2.description, expected)
}
#endif
}
2 changes: 0 additions & 2 deletions Tests/ParseSwiftTests/ParseCloudTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,6 @@ class ParseCloudTests: XCTestCase { // swiftlint:disable:this type_body_length
XCTAssertEqual(decoded, expected, "\"functionJobName\" key should be skipped by ParseEncoder")
}

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

func testCallFunctionCommand() throws {
let cloud = Cloud(functionJobName: "test")
Expand Down
2 changes: 0 additions & 2 deletions Tests/ParseSwiftTests/ParseConfigTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,6 @@ class ParseConfigTests: XCTestCase { // swiftlint:disable:this type_body_length
XCTAssertNil(command.body)
}

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

func testFetch() {
userLogin()
Expand Down
11 changes: 3 additions & 8 deletions Tests/ParseSwiftTests/ParseEncoderTests/TestParseEncoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,12 @@ class TestParseEncoder: XCTestCase {
_testRoundTrip(of: address)
}

#if !os(Linux) && !os(Android) && !os(Windows)
func testEncodingTopLevelStructuredClass() {
// Person is a class with multiple fields.
let expectedJSON = "{\"name\":\"Johnny Appleseed\",\"email\":\"[email protected]\"}".data(using: .utf8)!
let expectedJSON = "{\"email\":\"[email protected]\",\"name\":\"Johnny Appleseed\"}".data(using: .utf8)!
let person = Person.testValue
_testRoundTrip(of: person, expectedJSON: expectedJSON)
}
#endif

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

#if !os(Linux) && !os(Android) && !os(Windows)
#if !os(Linux) && !os(Android) && !os(Windows)
func testEncodingMultipleNestedContainersWithTheSameTopLevelKey() {
struct Model: Codable, Equatable {
let first: String
Expand Down Expand Up @@ -159,7 +157,6 @@ class TestParseEncoder: XCTestCase {
}
}
#endif

/*
func testEncodingConflictedTypeNestedContainersWithTheSameTopLevelKey() throws {
struct Model: Encodable, Equatable {
Expand Down Expand Up @@ -202,13 +199,11 @@ class TestParseEncoder: XCTestCase {
}*/

// MARK: - Output Formatting Tests
#if !os(Linux) && !os(Android) && !os(Windows)
func testEncodingOutputFormattingDefault() {
let expectedJSON = "{\"name\":\"Johnny Appleseed\",\"email\":\"[email protected]\"}".data(using: .utf8)!
let expectedJSON = "{\"email\":\"[email protected]\",\"name\":\"Johnny Appleseed\"}".data(using: .utf8)!
let person = Person.testValue
_testRoundTrip(of: person, expectedJSON: expectedJSON)
}
#endif
/*
func testEncodingOutputFormattingPrettyPrinted() {
let expectedJSON = "{\n \"name\" : \"Johnny Appleseed\",\n \"email\" : \"[email protected]\"\n}".data(using: .utf8)!
Expand Down
6 changes: 2 additions & 4 deletions Tests/ParseSwiftTests/ParseFileTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,6 @@ class ParseFileTests: XCTestCase { // swiftlint:disable:this type_body_length
XCTAssertEqual(parseFile1, parseFile2, "no urls, but localIds shoud be the same")
}

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

func testSave() throws {
guard let sampleData = "Hello World".data(using: .utf8) else {
Expand Down
6 changes: 2 additions & 4 deletions Tests/ParseSwiftTests/ParseGeoPointTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -88,19 +88,17 @@ class ParseGeoPointTests: XCTestCase {
}
}

#if !os(Linux) && !os(Android) && !os(Windows)
func testDebugString() throws {
let point = try ParseGeoPoint(latitude: 10, longitude: 20)
let expected = "ParseGeoPoint ({\"__type\":\"GeoPoint\",\"longitude\":20,\"latitude\":10})"
let expected = "ParseGeoPoint ({\"__type\":\"GeoPoint\",\"latitude\":10,\"longitude\":20})"
XCTAssertEqual(point.debugDescription, expected)
}

func testDescription() throws {
let point = try ParseGeoPoint(latitude: 10, longitude: 20)
let expected = "ParseGeoPoint ({\"__type\":\"GeoPoint\",\"longitude\":20,\"latitude\":10})"
let expected = "ParseGeoPoint ({\"__type\":\"GeoPoint\",\"latitude\":10,\"longitude\":20})"
XCTAssertEqual(point.description, expected)
}
#endif

// swiftlint:disable:next function_body_length
func testGeoUtilityDistance() throws {
Expand Down
14 changes: 7 additions & 7 deletions Tests/ParseSwiftTests/ParseLiveQueryTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ class ParseLiveQueryTests: XCTestCase {
return
}
// swiftlint:disable:next line_length
let expected = "{\"op\":\"connect\",\"applicationId\":\"applicationId\",\"clientKey\":\"clientKey\",\"masterKey\":\"masterKey\",\"installationId\":\"\(installationId)\"}"
let expected = "{\"applicationId\":\"applicationId\",\"clientKey\":\"clientKey\",\"installationId\":\"\(installationId)\",\"masterKey\":\"masterKey\",\"op\":\"connect\"}"
let message = StandardMessage(operation: .connect, additionalProperties: true)
let encoded = try ParseCoding.jsonEncoder()
.encode(message)
Expand All @@ -206,7 +206,7 @@ class ParseLiveQueryTests: XCTestCase {

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

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

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

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

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

func testPreliminaryResponseDecoding() throws {
let expected = "{\"op\":\"subscribed\",\"clientId\":\"message\",\"requestId\":1,\"installationId\":\"naw\"}"
let expected = "{\"clientId\":\"message\",\"installationId\":\"naw\",\"op\":\"subscribed\",\"requestId\":1}"
let message = PreliminaryMessageResponse(op: .subscribed,
requestId: 1,
clientId: "message",
Expand Down
9 changes: 2 additions & 7 deletions Tests/ParseSwiftTests/ParseObjectBatchTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,6 @@ class ParseObjectBatchTests: XCTestCase { // swiftlint:disable:this type_body_le
try ParseStorage.shared.deleteAll()
}

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

func testSaveAll() { // swiftlint:disable:this function_body_length cyclomatic_complexity
let score = GameScore(points: 10)
Expand Down Expand Up @@ -371,7 +368,6 @@ class ParseObjectBatchTests: XCTestCase { // swiftlint:disable:this type_body_le
}
}

#if !os(Linux) && !os(Android) && !os(Windows)
func testUpdateAllCommand() throws {
var score = GameScore(points: 10)
var score2 = GameScore(points: 20)
Expand All @@ -395,7 +391,7 @@ class ParseObjectBatchTests: XCTestCase { // swiftlint:disable:this type_body_le
}
let body = BatchCommand(requests: commands, transaction: false)
// swiftlint:disable:next line_length
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}"
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}"

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

func testUpdateAll() { // swiftlint:disable:this function_body_length cyclomatic_complexity
var score = GameScore(points: 10)
Expand Down
Loading

0 comments on commit e6457a4

Please sign in to comment.