From 2abc9897acb7eece9e05b60acc2fb69213cc3fd8 Mon Sep 17 00:00:00 2001 From: Paulo Andrade Date: Fri, 15 Jan 2016 13:01:50 +0000 Subject: [PATCH 1/8] Replaced failable initializers with the new throwing ones in JSONCodable.playground --- JSONCodable.playground/Contents.swift | 32 +++++++------------- JSONCodable.playground/contents.xcplayground | 2 +- JSONCodable.playground/timeline.xctimeline | 5 +++ 3 files changed, 17 insertions(+), 22 deletions(-) diff --git a/JSONCodable.playground/Contents.swift b/JSONCodable.playground/Contents.swift index 10e14c7..8af2eb3 100644 --- a/JSONCodable.playground/Contents.swift +++ b/JSONCodable.playground/Contents.swift @@ -54,31 +54,21 @@ We'll add conformance to `JSONDecodable`. You may also add conformance to `JSONC */ extension User: JSONDecodable { - init?(JSONDictionary: JSONObject) { - let decoder = JSONDecoder(object: JSONDictionary) - do { - id = try decoder.decode("id") - name = try decoder.decode("full_name") - email = try decoder.decode("email") - company = try decoder.decode("company") - friends = try decoder.decode("friends") - } - catch { - return nil - } + init(object: JSONObject) throws { + let decoder = JSONDecoder(object: object) + id = try decoder.decode("id") + name = try decoder.decode("full_name") + email = try decoder.decode("email") + company = try decoder.decode("company") + friends = try decoder.decode("friends") } } extension Company: JSONDecodable { - init?(JSONDictionary: JSONObject) { - let decoder = JSONDecoder(object: JSONDictionary) - do { - name = try decoder.decode("name") - address = try decoder.decode("address") - } - catch { - return nil - } + init(object: JSONObject) throws { + let decoder = JSONDecoder(object: object) + name = try decoder.decode("name") + address = try decoder.decode("address") } } diff --git a/JSONCodable.playground/contents.xcplayground b/JSONCodable.playground/contents.xcplayground index 0b96219..3de2b51 100644 --- a/JSONCodable.playground/contents.xcplayground +++ b/JSONCodable.playground/contents.xcplayground @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/JSONCodable.playground/timeline.xctimeline b/JSONCodable.playground/timeline.xctimeline index bf468af..6d30ad1 100644 --- a/JSONCodable.playground/timeline.xctimeline +++ b/JSONCodable.playground/timeline.xctimeline @@ -2,5 +2,10 @@ + + From 01d95bf7eb03bf917499b8ab620644b68f45f78c Mon Sep 17 00:00:00 2001 From: Paulo Andrade Date: Fri, 15 Jan 2016 14:02:51 +0000 Subject: [PATCH 2/8] Added option to encode null values --- JSONCodable/JSONCodable.swift | 2 +- JSONCodable/JSONEncodable.swift | 51 ++++++++++++++++++----------- JSONCodableTests/Food.swift | 3 +- JSONCodableTests/Fruit.swift | 3 +- JSONCodableTests/ImageAsset.swift | 2 +- JSONCodableTests/RegularTests.swift | 31 ++++++++++++++++++ JSONCodableTests/User.swift | 3 +- 7 files changed, 71 insertions(+), 24 deletions(-) diff --git a/JSONCodable/JSONCodable.swift b/JSONCodable/JSONCodable.swift index e62a58a..8f259d2 100644 --- a/JSONCodable/JSONCodable.swift +++ b/JSONCodable/JSONCodable.swift @@ -21,7 +21,7 @@ extension Bool: JSONCompatible {} extension Int: JSONCompatible {} extension JSONCompatible { - public func toJSON() throws -> AnyObject { + public func toJSON(encodeNulls encodeNulls: Bool) throws -> AnyObject { return self as! AnyObject } } diff --git a/JSONCodable/JSONEncodable.swift b/JSONCodable/JSONEncodable.swift index 543f9f7..b0c2f64 100644 --- a/JSONCodable/JSONEncodable.swift +++ b/JSONCodable/JSONEncodable.swift @@ -46,10 +46,15 @@ public enum JSONEncodableError: ErrorType, CustomStringConvertible { public protocol JSONEncodable { func toJSON() throws -> AnyObject + func toJSON(encodeNulls encodeNulls: Bool) throws -> AnyObject } public extension JSONEncodable { func toJSON() throws -> AnyObject { + return try toJSON(encodeNulls: false) + } + + func toJSON(encodeNulls encodeNulls: Bool) throws -> AnyObject { let mirror = Mirror(reflecting: self) guard let style = mirror.displayStyle where style == .Struct || style == .Class else { @@ -57,6 +62,7 @@ public extension JSONEncodable { } return try JSONEncoder.create({ (encoder) -> Void in + encoder.encodeNullValues = encodeNulls // loop through all properties (instance variables) for (labelMaybe, valueMaybe) in mirror.children { guard let label = labelMaybe else { @@ -94,11 +100,11 @@ public extension JSONEncodable { public extension Array {//where Element: JSONEncodable { private var wrapped: [Any] { return self.map{$0} } - public func toJSON() throws -> AnyObject { + public func toJSON(encodeNulls encodeNulls: Bool) throws -> AnyObject { var results: [AnyObject] = [] for item in self.wrapped { if let item = item as? JSONEncodable { - results.append(try item.toJSON()) + results.append(try item.toJSON(encodeNulls: encodeNulls)) } else { throw JSONEncodableError.ArrayIncompatibleTypeError(elementType: item.dynamicType) @@ -111,11 +117,11 @@ public extension Array {//where Element: JSONEncodable { // Dictionary convenience methods public extension Dictionary {//where Key: String, Value: JSONEncodable { - public func toJSON() throws -> AnyObject { + public func toJSON(encodeNulls encodeNulls: Bool) throws -> AnyObject { var result: [String: AnyObject] = [:] for (k, item) in self { if let item = item as? JSONEncodable { - result[String(k)] = try item.toJSON() + result[String(k)] = try item.toJSON(encodeNulls: encodeNulls) } else { throw JSONEncodableError.DictionaryIncompatibleTypeError(elementType: item.dynamicType) @@ -129,6 +135,7 @@ public extension Dictionary {//where Key: String, Value: JSONEncodable { public class JSONEncoder { var object = JSONObject() + public var encodeNullValues = false public static func create(@noescape setup: (encoder: JSONEncoder) throws -> Void) rethrows -> JSONObject { let encoder = JSONEncoder() @@ -146,20 +153,21 @@ public class JSONEncoder { // JSONEncodable public func encode(value: Encodable, key: String) throws { - let result = try value.toJSON() + let result = try value.toJSON(encodeNulls: encodeNullValues) object[key] = result } private func encode(value: JSONEncodable, key: String) throws { - let result = try value.toJSON() + let result = try value.toJSON(encodeNulls: encodeNullValues) object[key] = result } // JSONEncodable? public func encode(value: Encodable?, key: String) throws { guard let actual = value else { + if encodeNullValues { object[key] = NSNull() } return } - let result = try actual.toJSON() + let result = try actual.toJSON(encodeNulls: encodeNullValues) object[key] = result } @@ -168,19 +176,20 @@ public class JSONEncoder { guard let compatible = value.rawValue as? JSONCompatible else { return } - let result = try compatible.toJSON() + let result = try compatible.toJSON(encodeNulls: encodeNullValues) object[key] = result } // Enum? public func encode(value: Enum?, key: String) throws { guard let actual = value else { + if encodeNullValues { object[key] = NSNull() } return } guard let compatible = actual.rawValue as? JSONCompatible else { return } - let result = try compatible.toJSON() + let result = try compatible.toJSON(encodeNulls: encodeNullValues) object[key] = result } @@ -189,14 +198,14 @@ public class JSONEncoder { guard array.count > 0 else { return } - let result = try array.toJSON() + let result = try array.toJSON(encodeNulls: encodeNullValues) object[key] = result } public func encode(array: [JSONEncodable], key: String) throws { guard array.count > 0 else { return } - let result = try array.toJSON() + let result = try array.toJSON(encodeNulls: encodeNullValues) object[key] = result } private func encode(array: JSONArray, key: String) throws { @@ -204,19 +213,20 @@ public class JSONEncoder { return } let encodable = array.elementsMadeJSONEncodable() - let result = try encodable.toJSON() + let result = try encodable.toJSON(encodeNulls: encodeNullValues) object[key] = result } // [JSONEncodable]? public func encode(value: [Encodable]?, key: String) throws { guard let actual = value else { + if encodeNullValues { object[key] = NSNull() } return } guard actual.count > 0 else { return } - let result = try actual.toJSON() + let result = try actual.toJSON(encodeNulls: encodeNullValues) object[key] = result } @@ -226,7 +236,7 @@ public class JSONEncoder { return } let result = try value.flatMap { - try ($0.rawValue as? JSONCompatible)?.toJSON() + try ($0.rawValue as? JSONCompatible)?.toJSON(encodeNulls: encodeNullValues) } object[key] = result } @@ -234,13 +244,14 @@ public class JSONEncoder { // [Enum]? public func encode(value: [Enum]?, key: String) throws { guard let actual = value else { + if encodeNullValues { object[key] = NSNull() } return } guard actual.count > 0 else { return } let result = try actual.flatMap { - try ($0.rawValue as? JSONCompatible)?.toJSON() + try ($0.rawValue as? JSONCompatible)?.toJSON(encodeNulls: encodeNullValues) } object[key] = result } @@ -250,14 +261,14 @@ public class JSONEncoder { guard dictionary.count > 0 else { return } - let result = try dictionary.toJSON() + let result = try dictionary.toJSON(encodeNulls: encodeNullValues) object[key] = result } public func encode(dictionary: [String:JSONEncodable], key: String) throws { guard dictionary.count > 0 else { return } - let result = try dictionary.toJSON() + let result = try dictionary.toJSON(encodeNulls: encodeNullValues) object[key] = result } private func encode(dictionary: JSONDictionary, key: String) throws { @@ -265,19 +276,20 @@ public class JSONEncoder { return } let encodable = dictionary.valuesMadeJSONEncodable() - let result = try encodable.toJSON() + let result = try encodable.toJSON(encodeNulls: encodeNullValues) object[key] = result } // [String:JSONEncodable]? public func encode(value: [String:Encodable]?, key: String) throws { guard let actual = value else { + if encodeNullValues { object[key] = NSNull() } return } guard actual.count > 0 else { return } - let result = try actual.toJSON() + let result = try actual.toJSON(encodeNulls: encodeNullValues) object[key] = result } @@ -292,6 +304,7 @@ public class JSONEncoder { // JSONTransformable? public func encode(value: DecodedType?, key: String, transformer: JSONTransformer) throws { guard let actual = value else { + if encodeNullValues { object[key] = NSNull() } return } guard let result = transformer.encoding(actual) else { diff --git a/JSONCodableTests/Food.swift b/JSONCodableTests/Food.swift index 14847d1..364698d 100644 --- a/JSONCodableTests/Food.swift +++ b/JSONCodableTests/Food.swift @@ -38,8 +38,9 @@ extension Food: JSONCodable { cuisines = try decoder.decode("cuisines") } - func toJSON() throws -> AnyObject { + func toJSON(encodeNulls encodeNulls: Bool) throws -> AnyObject { return try JSONEncoder.create({ (encoder) -> Void in + encoder.encodeNullValues = encodeNulls try encoder.encode(name, key: "name") try encoder.encode(cuisines, key: "cuisines") }) diff --git a/JSONCodableTests/Fruit.swift b/JSONCodableTests/Fruit.swift index 3d981bc..b743267 100644 --- a/JSONCodableTests/Fruit.swift +++ b/JSONCodableTests/Fruit.swift @@ -30,8 +30,9 @@ extension Fruit: JSONCodable { color = try decoder.decode("color") } - func toJSON() throws -> AnyObject { + func toJSON(encodeNulls encodeNulls: Bool) throws -> AnyObject { return try JSONEncoder.create({ (encoder) -> Void in + encoder.encodeNullValues = encodeNulls try encoder.encode(name, key: "name") try encoder.encode(color, key: "color") }) diff --git a/JSONCodableTests/ImageAsset.swift b/JSONCodableTests/ImageAsset.swift index 5561b6c..98addc8 100644 --- a/JSONCodableTests/ImageAsset.swift +++ b/JSONCodableTests/ImageAsset.swift @@ -19,7 +19,7 @@ func ==(lhs: ImageAsset, rhs: ImageAsset) -> Bool { } extension ImageAsset: JSONEncodable { - func toJSON() throws -> AnyObject { + func toJSON(encodeNulls encodeNulls: Bool) throws -> AnyObject { return try JSONEncoder.create({ (encoder) -> Void in try encoder.encode(name, key: "name") try encoder.encode(uri, key: "uri", transformer: JSONTransformers.StringToNSURL) diff --git a/JSONCodableTests/RegularTests.swift b/JSONCodableTests/RegularTests.swift index ffbddf9..7c9b49c 100644 --- a/JSONCodableTests/RegularTests.swift +++ b/JSONCodableTests/RegularTests.swift @@ -23,6 +23,20 @@ class RegularTests: XCTestCase { ["id": 29, "full_name": "Jen Jackson"] ] ] + + let encodedValueWithNulls = [ + "id": 24, + "full_name": "John Appleseed", + "email": "john@appleseed.com", + "company": [ + "name": "Apple", + "address": "1 Infinite Loop, Cupertino, CA" + ], + "friends": [ + ["id": 27, "full_name": "Bob Jefferson", "email": NSNull(), "company": NSNull()], + ["id": 29, "full_name": "Jen Jackson", "email": NSNull(), "company": NSNull()] + ] + ] let decodedValue = User( id: 24, name: "John Appleseed", @@ -51,4 +65,21 @@ class RegularTests: XCTestCase { XCTAssertEqual(json as! [String : NSObject], encodedValue) } + func testNullEncoding() { + guard let json = try? decodedValue.toJSON(encodeNulls: true) else { + XCTFail() + return + } + + XCTAssertEqual(json as! [String : NSObject], encodedValueWithNulls) + } + + func testNullDecoding() { + guard let user = try? User(object: encodedValue) else { + XCTFail() + return + } + + XCTAssertEqual(user, decodedValue) + } } \ No newline at end of file diff --git a/JSONCodableTests/User.swift b/JSONCodableTests/User.swift index ed12c1e..a0c8210 100644 --- a/JSONCodableTests/User.swift +++ b/JSONCodableTests/User.swift @@ -26,8 +26,9 @@ func ==(lhs: User, rhs: User) -> Bool { } extension User: JSONEncodable { - func toJSON() throws -> AnyObject { + func toJSON(encodeNulls encodeNulls: Bool) throws -> AnyObject { return try JSONEncoder.create({ (encoder) -> Void in + encoder.encodeNullValues = encodeNulls try encoder.encode(id, key: "id") try encoder.encode(name, key: "full_name") try encoder.encode(email, key: "email") From 66271e649c70e18222cdcac1cb2953d292f04e12 Mon Sep 17 00:00:00 2001 From: Paulo Andrade Date: Fri, 15 Jan 2016 14:09:30 +0000 Subject: [PATCH 3/8] Missed a hunk on the playground --- JSONCodable.playground/Contents.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/JSONCodable.playground/Contents.swift b/JSONCodable.playground/Contents.swift index 8af2eb3..fe5ad77 100644 --- a/JSONCodable.playground/Contents.swift +++ b/JSONCodable.playground/Contents.swift @@ -105,7 +105,7 @@ We can instantiate `User` using one of provided initializers: - `init?(JSONString: String)` */ -let user = User(JSONDictionary: JSON)! +var user = try! User(object: JSON) print("Decoded: \n\(user)\n\n") From d87a4cb25a91af96fe4ebb3f501bacfbdea08f9c Mon Sep 17 00:00:00 2001 From: Paulo Andrade Date: Fri, 15 Jan 2016 15:37:31 +0000 Subject: [PATCH 4/8] Deleted redundant method --- JSONCodable/JSONEncodable.swift | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/JSONCodable/JSONEncodable.swift b/JSONCodable/JSONEncodable.swift index b0c2f64..6203ff4 100644 --- a/JSONCodable/JSONEncodable.swift +++ b/JSONCodable/JSONEncodable.swift @@ -152,11 +152,7 @@ public class JSONEncoder { */ // JSONEncodable - public func encode(value: Encodable, key: String) throws { - let result = try value.toJSON(encodeNulls: encodeNullValues) - object[key] = result - } - private func encode(value: JSONEncodable, key: String) throws { + public func encode(value: JSONEncodable, key: String) throws { let result = try value.toJSON(encodeNulls: encodeNullValues) object[key] = result } From da7e0960534082e414b7713d7d1170bdb8f3e17c Mon Sep 17 00:00:00 2001 From: Paulo Andrade Date: Fri, 15 Jan 2016 16:12:18 +0000 Subject: [PATCH 5/8] Added JSONCompatible extension to NSNull --- JSONCodable/JSONCodable.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/JSONCodable/JSONCodable.swift b/JSONCodable/JSONCodable.swift index 8f259d2..331708d 100644 --- a/JSONCodable/JSONCodable.swift +++ b/JSONCodable/JSONCodable.swift @@ -19,6 +19,7 @@ extension Double: JSONCompatible {} extension Float: JSONCompatible {} extension Bool: JSONCompatible {} extension Int: JSONCompatible {} +extension NSNull: JSONCompatible {} extension JSONCompatible { public func toJSON(encodeNulls encodeNulls: Bool) throws -> AnyObject { From dd0091bf96cf86111ef972a1ff7242c2f890813e Mon Sep 17 00:00:00 2001 From: Paulo Andrade Date: Fri, 15 Jan 2016 17:38:27 +0000 Subject: [PATCH 6/8] Change method visibility in for encode methods related to JSONArray and JSONDictionary to be consistent with the visibility of these types This also allows for clients (that include the code not the framework) to use the JSONArray and JSONDictionary helper extensions --- JSONCodable/JSONEncodable.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/JSONCodable/JSONEncodable.swift b/JSONCodable/JSONEncodable.swift index 6203ff4..2113292 100644 --- a/JSONCodable/JSONEncodable.swift +++ b/JSONCodable/JSONEncodable.swift @@ -204,8 +204,8 @@ public class JSONEncoder { let result = try array.toJSON(encodeNulls: encodeNullValues) object[key] = result } - private func encode(array: JSONArray, key: String) throws { guard array.count > 0 && array.elementsAreJSONEncodable() else { + func encode(array: JSONArray, key: String) throws { return } let encodable = array.elementsMadeJSONEncodable() @@ -267,8 +267,8 @@ public class JSONEncoder { let result = try dictionary.toJSON(encodeNulls: encodeNullValues) object[key] = result } - private func encode(dictionary: JSONDictionary, key: String) throws { guard dictionary.count > 0 && dictionary.valuesAreJSONEncodable() else { + func encode(dictionary: JSONDictionary, key: String) throws { return } let encodable = dictionary.valuesMadeJSONEncodable() From cee04c25673e4db5211d3e66d3651df46731d65f Mon Sep 17 00:00:00 2001 From: Paulo Andrade Date: Fri, 15 Jan 2016 17:39:18 +0000 Subject: [PATCH 7/8] Added option to encode empty collections This should probably be the default... --- JSONCodable/JSONEncodable.swift | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/JSONCodable/JSONEncodable.swift b/JSONCodable/JSONEncodable.swift index 2113292..04a1f2b 100644 --- a/JSONCodable/JSONEncodable.swift +++ b/JSONCodable/JSONEncodable.swift @@ -136,6 +136,7 @@ public extension Dictionary {//where Key: String, Value: JSONEncodable { public class JSONEncoder { var object = JSONObject() public var encodeNullValues = false + public var encodeEmptyCollections = false public static func create(@noescape setup: (encoder: JSONEncoder) throws -> Void) rethrows -> JSONObject { let encoder = JSONEncoder() @@ -191,21 +192,21 @@ public class JSONEncoder { // [JSONEncodable] public func encode(array: [Encodable], key: String) throws { - guard array.count > 0 else { + guard array.count > 0 || encodeEmptyCollections else { return } let result = try array.toJSON(encodeNulls: encodeNullValues) object[key] = result } public func encode(array: [JSONEncodable], key: String) throws { - guard array.count > 0 else { + guard array.count > 0 || encodeEmptyCollections else { return } let result = try array.toJSON(encodeNulls: encodeNullValues) object[key] = result } - guard array.count > 0 && array.elementsAreJSONEncodable() else { func encode(array: JSONArray, key: String) throws { + guard (array.count > 0 || encodeEmptyCollections) && array.elementsAreJSONEncodable() else { return } let encodable = array.elementsMadeJSONEncodable() @@ -219,7 +220,7 @@ public class JSONEncoder { if encodeNullValues { object[key] = NSNull() } return } - guard actual.count > 0 else { + guard actual.count > 0 || encodeEmptyCollections else { return } let result = try actual.toJSON(encodeNulls: encodeNullValues) @@ -228,7 +229,7 @@ public class JSONEncoder { // [Enum] public func encode(value: [Enum], key: String) throws { - guard value.count > 0 else { + guard value.count > 0 || encodeEmptyCollections else { return } let result = try value.flatMap { @@ -243,7 +244,7 @@ public class JSONEncoder { if encodeNullValues { object[key] = NSNull() } return } - guard actual.count > 0 else { + guard actual.count > 0 || encodeEmptyCollections else { return } let result = try actual.flatMap { @@ -254,21 +255,21 @@ public class JSONEncoder { // [String:JSONEncodable] public func encode(dictionary: [String:Encodable], key: String) throws { - guard dictionary.count > 0 else { + guard dictionary.count > 0 || encodeEmptyCollections else { return } let result = try dictionary.toJSON(encodeNulls: encodeNullValues) object[key] = result } public func encode(dictionary: [String:JSONEncodable], key: String) throws { - guard dictionary.count > 0 else { + guard dictionary.count > 0 || encodeEmptyCollections else { return } let result = try dictionary.toJSON(encodeNulls: encodeNullValues) object[key] = result } - guard dictionary.count > 0 && dictionary.valuesAreJSONEncodable() else { func encode(dictionary: JSONDictionary, key: String) throws { + guard (dictionary.count > 0 || encodeEmptyCollections) && dictionary.valuesAreJSONEncodable() else { return } let encodable = dictionary.valuesMadeJSONEncodable() @@ -282,7 +283,7 @@ public class JSONEncoder { if encodeNullValues { object[key] = NSNull() } return } - guard actual.count > 0 else { + guard actual.count > 0 || encodeEmptyCollections else { return } let result = try actual.toJSON(encodeNulls: encodeNullValues) From c204cc8d9ac7aea6a8943f90be69acda81130879 Mon Sep 17 00:00:00 2001 From: Paulo Andrade Date: Fri, 15 Jan 2016 19:12:43 +0000 Subject: [PATCH 8/8] Refactored JSONEncodable protocol to have single toJSON() method that received a set of options for encoding - JSONEncoder.create method also has a mandatory options parameter - Empty collections are now always encoded --- JSONCodable.playground/Contents.swift | 8 +- JSONCodable.playground/timeline.xctimeline | 2 +- JSONCodable/JSONCodable.swift | 2 +- JSONCodable/JSONEncodable.swift | 103 ++++++++------------- JSONCodable/JSONString.swift | 2 +- JSONCodableTests/EnumTests.swift | 4 +- JSONCodableTests/Food.swift | 7 +- JSONCodableTests/Fruit.swift | 10 +- JSONCodableTests/HelperTests.swift | 8 +- JSONCodableTests/ImageAsset.swift | 6 +- JSONCodableTests/RegularTests.swift | 12 +-- JSONCodableTests/TransformerTests.swift | 2 +- JSONCodableTests/User.swift | 7 +- 13 files changed, 75 insertions(+), 98 deletions(-) diff --git a/JSONCodable.playground/Contents.swift b/JSONCodable.playground/Contents.swift index fe5ad77..4ef77b1 100644 --- a/JSONCodable.playground/Contents.swift +++ b/JSONCodable.playground/Contents.swift @@ -31,14 +31,14 @@ We'll add conformance to `JSONEncodable`. You may also add conformance to `JSONC */ extension User: JSONEncodable { - func toJSON() throws -> AnyObject { - return try JSONEncoder.create({ (encoder) -> Void in + func toJSON(options: JSONEncodingOptions) throws -> AnyObject { + return try JSONEncoder.create(options) { (encoder) -> Void in try encoder.encode(id, key: "id") try encoder.encode(name, key: "full_name") try encoder.encode(email, key: "email") try encoder.encode(company, key: "company") try encoder.encode(friends, key: "friends") - }) + } } } @@ -116,7 +116,7 @@ And encode it to JSON using one of the provided methods: */ do { - let dict = try user.toJSON() + let dict = try user.toJSON([]) print("Encoded: \n\(dict as! JSONObject)\n\n") } catch { diff --git a/JSONCodable.playground/timeline.xctimeline b/JSONCodable.playground/timeline.xctimeline index 6d30ad1..46c39b3 100644 --- a/JSONCodable.playground/timeline.xctimeline +++ b/JSONCodable.playground/timeline.xctimeline @@ -3,7 +3,7 @@ version = "3.0"> diff --git a/JSONCodable/JSONCodable.swift b/JSONCodable/JSONCodable.swift index 331708d..d0b936a 100644 --- a/JSONCodable/JSONCodable.swift +++ b/JSONCodable/JSONCodable.swift @@ -22,7 +22,7 @@ extension Int: JSONCompatible {} extension NSNull: JSONCompatible {} extension JSONCompatible { - public func toJSON(encodeNulls encodeNulls: Bool) throws -> AnyObject { + public func toJSON(options: JSONEncodingOptions) throws -> AnyObject { return self as! AnyObject } } diff --git a/JSONCodable/JSONEncodable.swift b/JSONCodable/JSONEncodable.swift index 04a1f2b..d3cb3db 100644 --- a/JSONCodable/JSONEncodable.swift +++ b/JSONCodable/JSONEncodable.swift @@ -42,27 +42,28 @@ public enum JSONEncodableError: ErrorType, CustomStringConvertible { } } -// Struct -> Dictionary +public struct JSONEncodingOptions: OptionSetType { + public let rawValue: Int + public init(rawValue: Int) { + self.rawValue = rawValue; + } + public static let EncodeNulls = JSONEncodingOptions(rawValue: 1 << 0) +} public protocol JSONEncodable { - func toJSON() throws -> AnyObject - func toJSON(encodeNulls encodeNulls: Bool) throws -> AnyObject + func toJSON(options: JSONEncodingOptions) throws -> AnyObject } public extension JSONEncodable { - func toJSON() throws -> AnyObject { - return try toJSON(encodeNulls: false) - } - func toJSON(encodeNulls encodeNulls: Bool) throws -> AnyObject { + func toJSON(options: JSONEncodingOptions) throws -> AnyObject { let mirror = Mirror(reflecting: self) guard let style = mirror.displayStyle where style == .Struct || style == .Class else { throw JSONEncodableError.IncompatibleTypeError(elementType: self.dynamicType) } - return try JSONEncoder.create({ (encoder) -> Void in - encoder.encodeNullValues = encodeNulls + return try JSONEncoder.create(options) { (encoder) -> Void in // loop through all properties (instance variables) for (labelMaybe, valueMaybe) in mirror.children { guard let label = labelMaybe else { @@ -93,18 +94,18 @@ public extension JSONEncodable { throw JSONEncodableError.ChildIncompatibleTypeError(key: label, elementType: value.dynamicType) } } - }) + } } } public extension Array {//where Element: JSONEncodable { private var wrapped: [Any] { return self.map{$0} } - public func toJSON(encodeNulls encodeNulls: Bool) throws -> AnyObject { + public func toJSON(options: JSONEncodingOptions) throws -> AnyObject { var results: [AnyObject] = [] for item in self.wrapped { if let item = item as? JSONEncodable { - results.append(try item.toJSON(encodeNulls: encodeNulls)) + results.append(try item.toJSON(options)) } else { throw JSONEncodableError.ArrayIncompatibleTypeError(elementType: item.dynamicType) @@ -117,11 +118,11 @@ public extension Array {//where Element: JSONEncodable { // Dictionary convenience methods public extension Dictionary {//where Key: String, Value: JSONEncodable { - public func toJSON(encodeNulls encodeNulls: Bool) throws -> AnyObject { + public func toJSON(options: JSONEncodingOptions) throws -> AnyObject { var result: [String: AnyObject] = [:] for (k, item) in self { if let item = item as? JSONEncodable { - result[String(k)] = try item.toJSON(encodeNulls: encodeNulls) + result[String(k)] = try item.toJSON(options) } else { throw JSONEncodableError.DictionaryIncompatibleTypeError(elementType: item.dynamicType) @@ -135,11 +136,11 @@ public extension Dictionary {//where Key: String, Value: JSONEncodable { public class JSONEncoder { var object = JSONObject() - public var encodeNullValues = false - public var encodeEmptyCollections = false + public var options: JSONEncodingOptions = [] - public static func create(@noescape setup: (encoder: JSONEncoder) throws -> Void) rethrows -> JSONObject { + public static func create(options: JSONEncodingOptions, @noescape setup: (encoder: JSONEncoder) throws -> Void) rethrows -> JSONObject { let encoder = JSONEncoder() + encoder.options = options try setup(encoder: encoder) return encoder.object } @@ -154,17 +155,17 @@ public class JSONEncoder { // JSONEncodable public func encode(value: JSONEncodable, key: String) throws { - let result = try value.toJSON(encodeNulls: encodeNullValues) + let result = try value.toJSON(options) object[key] = result } // JSONEncodable? public func encode(value: Encodable?, key: String) throws { guard let actual = value else { - if encodeNullValues { object[key] = NSNull() } + if options.contains(.EncodeNulls) { object[key] = NSNull() } return } - let result = try actual.toJSON(encodeNulls: encodeNullValues) + let result = try actual.toJSON(options) object[key] = result } @@ -173,67 +174,55 @@ public class JSONEncoder { guard let compatible = value.rawValue as? JSONCompatible else { return } - let result = try compatible.toJSON(encodeNulls: encodeNullValues) + let result = try compatible.toJSON(options) object[key] = result } // Enum? public func encode(value: Enum?, key: String) throws { guard let actual = value else { - if encodeNullValues { object[key] = NSNull() } + if options.contains(.EncodeNulls) { object[key] = NSNull() } return } guard let compatible = actual.rawValue as? JSONCompatible else { return } - let result = try compatible.toJSON(encodeNulls: encodeNullValues) + let result = try compatible.toJSON(options) object[key] = result } // [JSONEncodable] public func encode(array: [Encodable], key: String) throws { - guard array.count > 0 || encodeEmptyCollections else { - return - } - let result = try array.toJSON(encodeNulls: encodeNullValues) + let result = try array.toJSON(options) object[key] = result } public func encode(array: [JSONEncodable], key: String) throws { - guard array.count > 0 || encodeEmptyCollections else { - return - } - let result = try array.toJSON(encodeNulls: encodeNullValues) + let result = try array.toJSON(options) object[key] = result } func encode(array: JSONArray, key: String) throws { - guard (array.count > 0 || encodeEmptyCollections) && array.elementsAreJSONEncodable() else { + guard array.elementsAreJSONEncodable() else { return } let encodable = array.elementsMadeJSONEncodable() - let result = try encodable.toJSON(encodeNulls: encodeNullValues) + let result = try encodable.toJSON(options) object[key] = result } // [JSONEncodable]? public func encode(value: [Encodable]?, key: String) throws { guard let actual = value else { - if encodeNullValues { object[key] = NSNull() } - return - } - guard actual.count > 0 || encodeEmptyCollections else { + if options.contains(.EncodeNulls) { object[key] = NSNull() } return } - let result = try actual.toJSON(encodeNulls: encodeNullValues) + let result = try actual.toJSON(options) object[key] = result } // [Enum] public func encode(value: [Enum], key: String) throws { - guard value.count > 0 || encodeEmptyCollections else { - return - } let result = try value.flatMap { - try ($0.rawValue as? JSONCompatible)?.toJSON(encodeNulls: encodeNullValues) + try ($0.rawValue as? JSONCompatible)?.toJSON(options) } object[key] = result } @@ -241,52 +230,40 @@ public class JSONEncoder { // [Enum]? public func encode(value: [Enum]?, key: String) throws { guard let actual = value else { - if encodeNullValues { object[key] = NSNull() } - return - } - guard actual.count > 0 || encodeEmptyCollections else { + if options.contains(.EncodeNulls) { object[key] = NSNull() } return } let result = try actual.flatMap { - try ($0.rawValue as? JSONCompatible)?.toJSON(encodeNulls: encodeNullValues) + try ($0.rawValue as? JSONCompatible)?.toJSON(options) } object[key] = result } // [String:JSONEncodable] public func encode(dictionary: [String:Encodable], key: String) throws { - guard dictionary.count > 0 || encodeEmptyCollections else { - return - } - let result = try dictionary.toJSON(encodeNulls: encodeNullValues) + let result = try dictionary.toJSON(options) object[key] = result } public func encode(dictionary: [String:JSONEncodable], key: String) throws { - guard dictionary.count > 0 || encodeEmptyCollections else { - return - } - let result = try dictionary.toJSON(encodeNulls: encodeNullValues) + let result = try dictionary.toJSON(options) object[key] = result } func encode(dictionary: JSONDictionary, key: String) throws { - guard (dictionary.count > 0 || encodeEmptyCollections) && dictionary.valuesAreJSONEncodable() else { + guard dictionary.valuesAreJSONEncodable() else { return } let encodable = dictionary.valuesMadeJSONEncodable() - let result = try encodable.toJSON(encodeNulls: encodeNullValues) + let result = try encodable.toJSON(options) object[key] = result } // [String:JSONEncodable]? public func encode(value: [String:Encodable]?, key: String) throws { guard let actual = value else { - if encodeNullValues { object[key] = NSNull() } - return - } - guard actual.count > 0 || encodeEmptyCollections else { + if options.contains(.EncodeNulls) { object[key] = NSNull() } return } - let result = try actual.toJSON(encodeNulls: encodeNullValues) + let result = try actual.toJSON(options) object[key] = result } @@ -301,7 +278,7 @@ public class JSONEncoder { // JSONTransformable? public func encode(value: DecodedType?, key: String, transformer: JSONTransformer) throws { guard let actual = value else { - if encodeNullValues { object[key] = NSNull() } + if options.contains(.EncodeNulls) { object[key] = NSNull() } return } guard let result = transformer.encoding(actual) else { diff --git a/JSONCodable/JSONString.swift b/JSONCodable/JSONString.swift index 74af215..da4ec37 100644 --- a/JSONCodable/JSONString.swift +++ b/JSONCodable/JSONString.swift @@ -16,7 +16,7 @@ public extension JSONEncodable { case is Bool, is Int, is Float, is Double: return String(self) default: - let json = try toJSON() + let json = try toJSON([]) let data = try NSJSONSerialization.dataWithJSONObject(json, options: NSJSONWritingOptions(rawValue: 0)) guard let string = NSString(data: data, encoding: NSUTF8StringEncoding) else { return "" diff --git a/JSONCodableTests/EnumTests.swift b/JSONCodableTests/EnumTests.swift index f8d0f4d..8395135 100644 --- a/JSONCodableTests/EnumTests.swift +++ b/JSONCodableTests/EnumTests.swift @@ -33,7 +33,7 @@ class EnumTests: XCTestCase { } func testEncodingEnum() { - guard let json = try? decodedValue.toJSON() else { + guard let json = try? decodedValue.toJSON([]) else { XCTFail() return } @@ -45,7 +45,7 @@ class EnumTests: XCTestCase { XCTAssertEqual(castedJSON, encodedValue) - guard let json2 = try? decodedValue2.toJSON() else { + guard let json2 = try? decodedValue2.toJSON([]) else { XCTFail() return } diff --git a/JSONCodableTests/Food.swift b/JSONCodableTests/Food.swift index 364698d..1f80136 100644 --- a/JSONCodableTests/Food.swift +++ b/JSONCodableTests/Food.swift @@ -38,11 +38,10 @@ extension Food: JSONCodable { cuisines = try decoder.decode("cuisines") } - func toJSON(encodeNulls encodeNulls: Bool) throws -> AnyObject { - return try JSONEncoder.create({ (encoder) -> Void in - encoder.encodeNullValues = encodeNulls + func toJSON(options: JSONEncodingOptions) throws -> AnyObject { + return try JSONEncoder.create(options) { (encoder) -> Void in try encoder.encode(name, key: "name") try encoder.encode(cuisines, key: "cuisines") - }) + } } } \ No newline at end of file diff --git a/JSONCodableTests/Fruit.swift b/JSONCodableTests/Fruit.swift index b743267..1450acb 100644 --- a/JSONCodableTests/Fruit.swift +++ b/JSONCodableTests/Fruit.swift @@ -30,11 +30,13 @@ extension Fruit: JSONCodable { color = try decoder.decode("color") } - func toJSON(encodeNulls encodeNulls: Bool) throws -> AnyObject { - return try JSONEncoder.create({ (encoder) -> Void in - encoder.encodeNullValues = encodeNulls + func toJSON(options: JSONEncodingOptions) throws -> AnyObject { + return try JSONEncoder.create(options) { (encoder) -> Void in + NSLog("a") try encoder.encode(name, key: "name") + NSLog("b") try encoder.encode(color, key: "color") - }) + NSLog("c") + } } } \ No newline at end of file diff --git a/JSONCodableTests/HelperTests.swift b/JSONCodableTests/HelperTests.swift index 0af0c17..3ecc816 100644 --- a/JSONCodableTests/HelperTests.swift +++ b/JSONCodableTests/HelperTests.swift @@ -24,10 +24,10 @@ class HelperTests: XCTestCase { let notEncodableArray:[NotEncodable] = [NotEncodable()] XCTAssert(!notEncodableArray.elementsAreJSONEncodable(), "Array of type [NotEncodable] should not be encodable") - let _ = try? JSONEncoder.create({ (encoder) -> Void in + let _ = try? JSONEncoder.create([]) { (encoder) -> Void in try encoder.encode(intArray, key: "intArray") try encoder.encode(encodableArray, key: "encodableArray") - }) + } } @@ -41,10 +41,10 @@ class HelperTests: XCTestCase { let notEncodableDict:[String:NotEncodable] = ["a":NotEncodable()] XCTAssert(!notEncodableDict.valuesAreJSONEncodable(), "Dictionary of type [String:NotEncodable] should not be encodable") - let _ = try? JSONEncoder.create({ (encoder) -> Void in + let _ = try? JSONEncoder.create([]) { (encoder) -> Void in try encoder.encode(intDict, key: "intArray") try encoder.encode(encodableDict, key: "encodableArray") - }) + } } } diff --git a/JSONCodableTests/ImageAsset.swift b/JSONCodableTests/ImageAsset.swift index 98addc8..28e30f0 100644 --- a/JSONCodableTests/ImageAsset.swift +++ b/JSONCodableTests/ImageAsset.swift @@ -19,11 +19,11 @@ func ==(lhs: ImageAsset, rhs: ImageAsset) -> Bool { } extension ImageAsset: JSONEncodable { - func toJSON(encodeNulls encodeNulls: Bool) throws -> AnyObject { - return try JSONEncoder.create({ (encoder) -> Void in + func toJSON(options: JSONEncodingOptions) throws -> AnyObject { + return try JSONEncoder.create(options) { (encoder) -> Void in try encoder.encode(name, key: "name") try encoder.encode(uri, key: "uri", transformer: JSONTransformers.StringToNSURL) - }) + } } } diff --git a/JSONCodableTests/RegularTests.swift b/JSONCodableTests/RegularTests.swift index 7c9b49c..5410f31 100644 --- a/JSONCodableTests/RegularTests.swift +++ b/JSONCodableTests/RegularTests.swift @@ -19,8 +19,8 @@ class RegularTests: XCTestCase { "address": "1 Infinite Loop, Cupertino, CA" ], "friends": [ - ["id": 27, "full_name": "Bob Jefferson"], - ["id": 29, "full_name": "Jen Jackson"] + ["id": 27, "full_name": "Bob Jefferson", "friends": []], + ["id": 29, "full_name": "Jen Jackson", "friends": []] ] ] @@ -33,8 +33,8 @@ class RegularTests: XCTestCase { "address": "1 Infinite Loop, Cupertino, CA" ], "friends": [ - ["id": 27, "full_name": "Bob Jefferson", "email": NSNull(), "company": NSNull()], - ["id": 29, "full_name": "Jen Jackson", "email": NSNull(), "company": NSNull()] + ["id": 27, "full_name": "Bob Jefferson", "email": NSNull(), "company": NSNull(), "friends": []], + ["id": 29, "full_name": "Jen Jackson", "email": NSNull(), "company": NSNull(), "friends": []] ] ] let decodedValue = User( @@ -57,7 +57,7 @@ class RegularTests: XCTestCase { } func testEncodingRegular() { - guard let json = try? decodedValue.toJSON() else { + guard let json = try? decodedValue.toJSON([]) else { XCTFail() return } @@ -66,7 +66,7 @@ class RegularTests: XCTestCase { } func testNullEncoding() { - guard let json = try? decodedValue.toJSON(encodeNulls: true) else { + guard let json = try? decodedValue.toJSON([.EncodeNulls]) else { XCTFail() return } diff --git a/JSONCodableTests/TransformerTests.swift b/JSONCodableTests/TransformerTests.swift index f323923..020b1a3 100644 --- a/JSONCodableTests/TransformerTests.swift +++ b/JSONCodableTests/TransformerTests.swift @@ -29,7 +29,7 @@ class TransformerTests: XCTestCase { } func testEncodingTransformer() { - guard let json = try? decodedValue.toJSON() else { + guard let json = try? decodedValue.toJSON([]) else { XCTFail() return } diff --git a/JSONCodableTests/User.swift b/JSONCodableTests/User.swift index a0c8210..4977c88 100644 --- a/JSONCodableTests/User.swift +++ b/JSONCodableTests/User.swift @@ -26,15 +26,14 @@ func ==(lhs: User, rhs: User) -> Bool { } extension User: JSONEncodable { - func toJSON(encodeNulls encodeNulls: Bool) throws -> AnyObject { - return try JSONEncoder.create({ (encoder) -> Void in - encoder.encodeNullValues = encodeNulls + func toJSON(options: JSONEncodingOptions) throws -> AnyObject { + return try JSONEncoder.create(options) { (encoder) -> Void in try encoder.encode(id, key: "id") try encoder.encode(name, key: "full_name") try encoder.encode(email, key: "email") try encoder.encode(company, key: "company") try encoder.encode(friends, key: "friends") - }) + } } }