From 9056435a047b433b1eef74fe91b83db53ddb888a Mon Sep 17 00:00:00 2001 From: Lukas Schmidt Date: Wed, 17 Aug 2016 17:07:28 +0200 Subject: [PATCH 1/9] Better readability fortransformers (#46) * Changes default transformers to static function to enable better readability while consuming them * Uses static variables on extension instead of static functions * make old api public to not break compatibility --- JSONCodable/JSONTransformer.swift | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/JSONCodable/JSONTransformer.swift b/JSONCodable/JSONTransformer.swift index 68c9646..c0804cd 100644 --- a/JSONCodable/JSONTransformer.swift +++ b/JSONCodable/JSONTransformer.swift @@ -47,3 +47,13 @@ public struct JSONTransformers { encoding: {dateTimeFormatter.string(from: $0)}) #endif } + +public extension JSONTransformer { + static var stringToURL: JSONTransformer{ + return JSONTransformers.StringToNSURL + } + + static var stringToDate: JSONTransformer{ + return JSONTransformers.StringToNSDate + } +} From 467a07d534883dfda259be63e9f352914de0735e Mon Sep 17 00:00:00 2001 From: Kevin Chen Date: Thu, 22 Sep 2016 20:32:10 -0700 Subject: [PATCH 2/9] Xcode GM supported (#53) * Updated for Swift 3 * Fixed tests * Add support exporting nested JSON objects * test for encoding nested JSON objects and remove unneeded imports. * implementation for top level array and tests for Decoding * - Solved dynamicType and String initializer * - Fixed syntax upgrade issues * - Fixed unit tests * - Updated the Array extension to adopt swift 3 new syntax * - Fixed the Mirror doesn't have displayStyle for Swift default type, like String, Int, etc * - Updated JSONCompatible toJSON bug * Xcode GM supported * Upgraded version --- JSONCodable.playground/Contents.swift | 6 +- JSONCodable.podspec | 2 +- JSONCodable.xcodeproj/project.pbxproj | 54 +++++- .../xcschemes/JSONCodable OSX.xcscheme | 2 +- .../xcschemes/JSONCodable iOS.xcscheme | 2 +- JSONCodable/JSONCodable.swift | 12 +- JSONCodable/JSONDecodable.swift | 179 +++++++++--------- JSONCodable/JSONEncodable+Mirror.swift | 6 +- JSONCodable/JSONEncodable.swift | 175 +++++++++-------- JSONCodable/JSONHelpers.swift | 53 +++--- JSONCodable/JSONString.swift | 54 ++---- JSONCodable/JSONTransformer.swift | 30 ++- JSONCodableTests/ClassInheritance.swift | 2 +- JSONCodableTests/Company.swift | 3 +- JSONCodableTests/EncodeNestingTests.swift | 33 ++++ JSONCodableTests/EnumTests.swift | 2 +- JSONCodableTests/Food.swift | 5 +- JSONCodableTests/Fruit.swift | 37 ++-- JSONCodableTests/HelperTests.swift | 60 +++--- JSONCodableTests/ImageAsset.swift | 29 ++- JSONCodableTests/Messages.swift | 46 +++++ JSONCodableTests/NestItem.swift | 5 +- JSONCodableTests/PropertyItem.swift | 44 +++++ JSONCodableTests/RegularTests.swift | 84 ++++---- JSONCodableTests/TransformerTests.swift | 60 +++++- JSONCodableTests/User.swift | 11 +- JSONCodableTests/UtilityTests.swift | 2 +- 27 files changed, 604 insertions(+), 394 deletions(-) create mode 100644 JSONCodableTests/EncodeNestingTests.swift create mode 100644 JSONCodableTests/Messages.swift create mode 100644 JSONCodableTests/PropertyItem.swift diff --git a/JSONCodable.playground/Contents.swift b/JSONCodable.playground/Contents.swift index 4b8b338..e19a88b 100644 --- a/JSONCodable.playground/Contents.swift +++ b/JSONCodable.playground/Contents.swift @@ -31,7 +31,7 @@ We'll add conformance to `JSONEncodable`. You may also add conformance to `JSONC */ extension User: JSONEncodable { - func toJSON() throws -> AnyObject { + func toJSON() throws -> Any { return try JSONEncoder.create({ (encoder) -> Void in try encoder.encode(id, key: "id") try encoder.encode(name, key: "full_name") @@ -83,7 +83,7 @@ You can open the console and see the output using `CMD + SHIFT + Y` or ⇧⌘Y. Let's work with an incoming JSON Dictionary: */ -let JSON = [ +let JSON: [String: Any] = [ "id": 24, "full_name": "John Appleseed", "email": "john@appleseed.com", @@ -114,5 +114,7 @@ And encode it to JSON using one of the provided methods: - `func JSONString() throws -> String` */ +try! 1.toJSON() + let dict = try! user.toJSON() print("Encoded: \n\(dict as! JSONObject)\n\n") diff --git a/JSONCodable.podspec b/JSONCodable.podspec index 720ef4c..5c49cc9 100644 --- a/JSONCodable.podspec +++ b/JSONCodable.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'JSONCodable' - s.version = '2.1' + s.version = '2.2' s.ios.deployment_target = '8.0' s.osx.deployment_target = '10.10' s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/JSONCodable.xcodeproj/project.pbxproj b/JSONCodable.xcodeproj/project.pbxproj index de323e4..7e2df37 100644 --- a/JSONCodable.xcodeproj/project.pbxproj +++ b/JSONCodable.xcodeproj/project.pbxproj @@ -37,6 +37,9 @@ A1B71C7E1D37E90B006DA33A /* MirrorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B71C7D1D37E90B006DA33A /* MirrorTests.swift */; }; A1B71C801D37E982006DA33A /* ClassInheritance.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B71C7F1D37E982006DA33A /* ClassInheritance.swift */; }; A1B71C811D37EA92006DA33A /* JSONEncodable+Mirror.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B71C7B1D37E6BD006DA33A /* JSONEncodable+Mirror.swift */; }; + BD885BBE1D17358E00CA767A /* EncodeNestingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD885BBD1D17358E00CA767A /* EncodeNestingTests.swift */; }; + BD885BC01D173A0700CA767A /* PropertyItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD885BBF1D173A0700CA767A /* PropertyItem.swift */; }; + BDD667CC1D1F3572003F94D7 /* Messages.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDD667CB1D1F3572003F94D7 /* Messages.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -76,6 +79,9 @@ A1B71C7B1D37E6BD006DA33A /* JSONEncodable+Mirror.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "JSONEncodable+Mirror.swift"; sourceTree = ""; }; A1B71C7D1D37E90B006DA33A /* MirrorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MirrorTests.swift; sourceTree = ""; }; A1B71C7F1D37E982006DA33A /* ClassInheritance.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClassInheritance.swift; sourceTree = ""; }; + BD885BBD1D17358E00CA767A /* EncodeNestingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EncodeNestingTests.swift; sourceTree = ""; }; + BD885BBF1D173A0700CA767A /* PropertyItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PropertyItem.swift; sourceTree = ""; }; + BDD667CB1D1F3572003F94D7 /* Messages.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Messages.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -118,6 +124,8 @@ isa = PBXGroup; children = ( 5211CD091CE2EBFB0097F255 /* NestItem.swift */, + BDD667CB1D1F3572003F94D7 /* Messages.swift */, + BD885BBF1D173A0700CA767A /* PropertyItem.swift */, 9E455C021BCE1C1E00070A4F /* Fruit.swift */, 9E8E07231BD3F15800F98421 /* Food.swift */, 9E455C041BCE1D0700070A4F /* User.swift */, @@ -134,6 +142,7 @@ 9ECF00C31BCF82F5008D557C /* HelperTests.swift */, 52E8F44E1C9087D200F40F7F /* UtilityTests.swift */, 9E455C0A1BCE1F0100070A4F /* RegularTests.swift */, + BD885BBD1D17358E00CA767A /* EncodeNestingTests.swift */, 9E455BF91BCE185B00070A4F /* EnumTests.swift */, 9ECF00BF1BCE251B008D557C /* TransformerTests.swift */, A1B71C7D1D37E90B006DA33A /* MirrorTests.swift */, @@ -266,7 +275,7 @@ 9EDF80101B59CFCE00E4A2D6 /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0710; + LastUpgradeCheck = 0800; TargetAttributes = { 9E455BF61BCE185B00070A4F = { CreatedOnToolsVersion = 7.0.1; @@ -276,6 +285,7 @@ }; 9EDB39221B59D01D00C63019 = { CreatedOnToolsVersion = 7.0; + LastSwiftMigration = 0800; }; }; }; @@ -335,10 +345,13 @@ 9ECF00C21BCF6E43008D557C /* ImageAsset.swift in Sources */, 9E455C031BCE1C1E00070A4F /* Fruit.swift in Sources */, 9ECF00C01BCE251B008D557C /* TransformerTests.swift in Sources */, + BDD667CC1D1F3572003F94D7 /* Messages.swift in Sources */, 9E455BFA1BCE185B00070A4F /* EnumTests.swift in Sources */, 9E8E07241BD3F15800F98421 /* Food.swift in Sources */, + BD885BBE1D17358E00CA767A /* EncodeNestingTests.swift in Sources */, 9E455C0B1BCE1F0100070A4F /* RegularTests.swift in Sources */, 9E455C051BCE1D0700070A4F /* User.swift in Sources */, + BD885BC01D173A0700CA767A /* PropertyItem.swift in Sources */, 9E455C091BCE1DE100070A4F /* Company.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -427,6 +440,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 3.0; }; name = Debug; }; @@ -467,6 +481,8 @@ PRODUCT_BUNDLE_IDENTIFIER = com.matthewcheok.JSONCodableTests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 3.0; }; name = Release; }; @@ -522,6 +538,7 @@ SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 3.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -573,6 +590,8 @@ PRODUCT_NAME = JSONCodable; SDKROOT = iphoneos; SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 3.0; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; @@ -633,6 +652,7 @@ SDKROOT = macosx; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 3.0; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; @@ -683,6 +703,8 @@ PRODUCT_NAME = JSONCodable; SDKROOT = macosx; SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 3.0; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; @@ -691,7 +713,22 @@ 9EDF80141B59CFCE00E4A2D6 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 8.0; MACOSX_DEPLOYMENT_TARGET = 10.10; ONLY_ACTIVE_ARCH = YES; @@ -701,6 +738,21 @@ 9EDF80151B59CFCE00E4A2D6 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 8.0; MACOSX_DEPLOYMENT_TARGET = 10.10; }; diff --git a/JSONCodable.xcodeproj/xcshareddata/xcschemes/JSONCodable OSX.xcscheme b/JSONCodable.xcodeproj/xcshareddata/xcschemes/JSONCodable OSX.xcscheme index 9d9d6a7..6156403 100644 --- a/JSONCodable.xcodeproj/xcshareddata/xcschemes/JSONCodable OSX.xcscheme +++ b/JSONCodable.xcodeproj/xcshareddata/xcschemes/JSONCodable OSX.xcscheme @@ -1,6 +1,6 @@ AnyObject { - return self as! AnyObject - } + public func toJSON() throws -> Any { + return self + } } - -// Swift 2 Shims - -#if !swift(>=3.0) - typealias ErrorProtocol = ErrorType -#endif \ No newline at end of file diff --git a/JSONCodable/JSONDecodable.swift b/JSONCodable/JSONDecodable.swift index 1cb1c30..46cf0ba 100644 --- a/JSONCodable/JSONDecodable.swift +++ b/JSONCodable/JSONDecodable.swift @@ -8,38 +8,38 @@ // Decoding Errors -public enum JSONDecodableError: ErrorProtocol, CustomStringConvertible { - case MissingTypeError( +public enum JSONDecodableError: Error, CustomStringConvertible { + case missingTypeError( key: String ) - case IncompatibleTypeError( + case incompatibleTypeError( key: String, elementType: Any.Type, expectedType: Any.Type ) - case ArrayTypeExpectedError( + case arrayTypeExpectedError( key: String, elementType: Any.Type ) - case DictionaryTypeExpectedError( + case dictionaryTypeExpectedError( key: String, elementType: Any.Type ) - case TransformerFailedError( + case transformerFailedError( key: String ) public var description: String { switch self { - case let .MissingTypeError(key: key): + case let .missingTypeError(key: key): return "JSONDecodableError: Missing value for key \(key)" - case let .IncompatibleTypeError(key: key, elementType: elementType, expectedType: expectedType): + case let .incompatibleTypeError(key: key, elementType: elementType, expectedType: expectedType): return "JSONDecodableError: Incompatible type for key \(key); Got \(elementType) instead of \(expectedType)" - case let .ArrayTypeExpectedError(key: key, elementType: elementType): + case let .arrayTypeExpectedError(key: key, elementType: elementType): return "JSONDecodableError: Got \(elementType) instead of an array for key \(key)" - case let .DictionaryTypeExpectedError(key: key, elementType: elementType): + case let .dictionaryTypeExpectedError(key: key, elementType: elementType): return "JSONDecodableError: Got \(elementType) instead of a dictionary for key \(key)" - case let .TransformerFailedError(key: key): + case let .transformerFailedError(key: key): return "JSONDecodableError: Transformer failed for key \(key)" } } @@ -49,9 +49,16 @@ public enum JSONDecodableError: ErrorProtocol, CustomStringConvertible { public protocol JSONDecodable { init(object: JSONObject) throws + init(object: [JSONObject]) throws } public extension JSONDecodable { + /// initialize with top-level Array JSON data + public init(object: [JSONObject]) throws { + // use empty string key + try self.init(object:["": object]) + } + public init?(optional: JSONObject) { do { try self.init(object: optional) @@ -62,10 +69,10 @@ public extension JSONDecodable { } public extension Array where Element: JSONDecodable { - init(JSONArray: [AnyObject]) throws { + init(JSONArray: [Any]) throws { self.init(try JSONArray.flatMap { - guard let json = $0 as? [String : AnyObject] else { - throw JSONDecodableError.DictionaryTypeExpectedError(key: "n/a", elementType: $0.dynamicType) + guard let json = $0 as? [String : Any] else { + throw JSONDecodableError.dictionaryTypeExpectedError(key: "n/a", elementType: type(of: $0)) } return try Element(object: json) }) @@ -83,7 +90,7 @@ public class JSONDecoder { /// Get index from `"[0]"` formatted `String` /// returns `nil` if invalid format (i.e. no brackets or contents not an `Int`) - internal func parseArrayIndex(key:String) -> Int? { + internal func parseArrayIndex(_ key:String) -> Int? { var chars = key.characters let first = chars.popFirst() let last = chars.popLast() @@ -94,174 +101,168 @@ public class JSONDecoder { } } - private func get(key: String) -> AnyObject? { - #if !swift(>=3.0) - let keys = key.stringByReplacingOccurrencesOfString("[", withString: ".[") - .componentsSeparatedByString(".") - #else - let keys = key.replacingOccurrences(of: "[", with: ".[").componentsSeparated(by: ".") - #endif - - let result = keys.reduce(object as AnyObject?) { + private func get(_ key: String) -> Any? { + let keys = key.replacingOccurrences(of: "[", with: ".[").components(separatedBy: ".") + let result = keys.reduce(object as Any?) { value, key in switch value { - case let dict as [String: AnyObject]: - return dict[key] - - case let arr as [AnyObject]: - guard let index = parseArrayIndex(key) else { - return nil - } - guard (0..(key: String) throws -> Compatible { + public func decode(_ key: String) throws -> Compatible { guard let value = get(key) else { - throw JSONDecodableError.MissingTypeError(key: key) + throw JSONDecodableError.missingTypeError(key: key) } guard let compatible = value as? Compatible else { - throw JSONDecodableError.IncompatibleTypeError(key: key, elementType: value.dynamicType, expectedType: Compatible.self) + throw JSONDecodableError.incompatibleTypeError(key: key, elementType: type(of: value), expectedType: Compatible.self) } return compatible } // JSONCompatible? - public func decode(key: String) throws -> Compatible? { + public func decode(_ key: String) throws -> Compatible? { return (get(key) ?? object[key]) as? Compatible } // JSONDecodable - public func decode(key: String) throws -> Decodable { + public func decode(_ key: String) throws -> Decodable { guard let value = get(key) else { - throw JSONDecodableError.MissingTypeError(key: key) + throw JSONDecodableError.missingTypeError(key: key) } guard let object = value as? JSONObject else { - throw JSONDecodableError.DictionaryTypeExpectedError(key: key, elementType: value.dynamicType) + throw JSONDecodableError.dictionaryTypeExpectedError(key: key, elementType: type(of: value)) } return try Decodable(object: object) } // JSONDecodable? - public func decode(key: String) throws -> Decodable? { + public func decode(_ key: String) throws -> Decodable? { guard let value = get(key) else { return nil } guard let object = value as? JSONObject else { - throw JSONDecodableError.DictionaryTypeExpectedError(key: key, elementType: value.dynamicType) + throw JSONDecodableError.dictionaryTypeExpectedError(key: key, elementType: type(of: value)) } return try Decodable(object: object) } // Enum - public func decode(key: String) throws -> Enum { + public func decode(_ key: String) throws -> Enum { guard let value = get(key) else { - throw JSONDecodableError.MissingTypeError(key: key) + throw JSONDecodableError.missingTypeError(key: key) } guard let raw = value as? Enum.RawValue else { - throw JSONDecodableError.IncompatibleTypeError(key: key, elementType: value.dynamicType, expectedType: Enum.RawValue.self) + throw JSONDecodableError.incompatibleTypeError(key: key, elementType: type(of: value), expectedType: Enum.RawValue.self) } guard let result = Enum(rawValue: raw) else { - throw JSONDecodableError.IncompatibleTypeError(key: key, elementType: Enum.RawValue.self, expectedType: Enum.self) + throw JSONDecodableError.incompatibleTypeError(key: key, elementType: Enum.RawValue.self, expectedType: Enum.self) } return result } // Enum? - public func decode(key: String) throws -> Enum? { + public func decode(_ key: String) throws -> Enum? { guard let value = get(key) else { return nil } guard let raw = value as? Enum.RawValue else { - throw JSONDecodableError.IncompatibleTypeError(key: key, elementType: value.dynamicType, expectedType: Enum.RawValue.self) + throw JSONDecodableError.incompatibleTypeError(key: key, elementType: type(of: value), expectedType: Enum.RawValue.self) } guard let result = Enum(rawValue: raw) else { - throw JSONDecodableError.IncompatibleTypeError(key: key, elementType: Enum.RawValue.self, expectedType: Enum.self) + throw JSONDecodableError.incompatibleTypeError(key: key, elementType: Enum.RawValue.self, expectedType: Enum.self) } return result } // [JSONCompatible] - public func decode(key: String) throws -> [Element] { + public func decode(_ key: String) throws -> [Element] { guard let value = get(key) else { return [] } guard let array = value as? [Element] else { - throw JSONDecodableError.IncompatibleTypeError(key: key, elementType: value.dynamicType, expectedType: [Element].self) + throw JSONDecodableError.incompatibleTypeError(key: key, elementType: type(of: value), expectedType: [Element].self) } return array } // [JSONCompatible]? - public func decode(key: String) throws -> [Element]? { + public func decode(_ key: String) throws -> [Element]? { guard let value = get(key) else { return nil } guard let array = value as? [Element] else { - throw JSONDecodableError.IncompatibleTypeError(key: key, elementType: value.dynamicType, expectedType: [Element].self) + throw JSONDecodableError.incompatibleTypeError(key: key, elementType: type(of: value), expectedType: [Element].self) } return array } // [JSONDecodable] - public func decode(key: String) throws -> [Element] { + public func decode(_ key: String) throws -> [Element] { guard let value = get(key) else { return [] } guard let array = value as? [JSONObject] else { - throw JSONDecodableError.ArrayTypeExpectedError(key: key, elementType: value.dynamicType) + throw JSONDecodableError.arrayTypeExpectedError(key: key, elementType: type(of: value)) } return try array.flatMap { try Element(object: $0)} } // [JSONDecodable]? - public func decode(key: String) throws -> [Element]? { + public func decode(_ key: String) throws -> [Element]? { guard let value = get(key) else { return nil } guard let array = value as? [JSONObject] else { - throw JSONDecodableError.ArrayTypeExpectedError(key: key, elementType: value.dynamicType) + throw JSONDecodableError.arrayTypeExpectedError(key: key, elementType: type(of: value)) } return try array.flatMap { try Element(object: $0)} } - + // [[JSONDecodable]] - public func decode(key: String) throws -> [[Element]] { + public func decode(_ key: String) throws -> [[Element]] { guard let value = get(key) else { return [] } guard let array = value as? [[JSONObject]] else { - throw JSONDecodableError.ArrayTypeExpectedError(key: key, elementType: value.dynamicType) + throw JSONDecodableError.arrayTypeExpectedError(key: key, elementType: type(of: value)) } var res:[[Element]] = [] - + for x in array { let nested = try x.flatMap { try Element(object: $0)} res.append(nested) } return res } - + // [[JSONCompatible]] - public func decode(key: String) throws -> [[Element]] { + public func decode(_ key: String) throws -> [[Element]] { guard let value = get(key) else { return [] } guard let array = value as? [[Element]] else { - throw JSONDecodableError.IncompatibleTypeError(key: key, elementType: value.dynamicType, expectedType: [Element].self) + throw JSONDecodableError.incompatibleTypeError(key: key, elementType: type(of: value), expectedType: [Element].self) } var res:[[Element]] = [] - + for x in array { res.append(x) } @@ -269,56 +270,56 @@ public class JSONDecoder { } // [Enum] - public func decode(key: String) throws -> [Enum] { + public func decode(_ key: String) throws -> [Enum] { guard let value = get(key) else { return [] } guard let array = value as? [Enum.RawValue] else { - throw JSONDecodableError.ArrayTypeExpectedError(key: key, elementType: value.dynamicType) + throw JSONDecodableError.arrayTypeExpectedError(key: key, elementType: type(of: value)) } return array.flatMap { Enum(rawValue: $0) } } // [Enum]? - public func decode(key: String) throws -> [Enum]? { + public func decode(_ key: String) throws -> [Enum]? { guard let value = get(key) else { return nil } guard let array = value as? [Enum.RawValue] else { - throw JSONDecodableError.ArrayTypeExpectedError(key: key, elementType: value.dynamicType) + throw JSONDecodableError.arrayTypeExpectedError(key: key, elementType: type(of: value)) } return array.flatMap { Enum(rawValue: $0) } } // [String:JSONCompatible] - public func decode(key: String) throws -> [String: Value] { + public func decode(_ key: String) throws -> [String: Value] { guard let value = get(key) else { - throw JSONDecodableError.MissingTypeError(key: key) + throw JSONDecodableError.missingTypeError(key: key) } guard let dictionary = value as? [String: Value] else { - throw JSONDecodableError.IncompatibleTypeError(key: key, elementType: value.dynamicType, expectedType: [String: Value].self) + throw JSONDecodableError.incompatibleTypeError(key: key, elementType: type(of: value), expectedType: [String: Value].self) } return dictionary } // [String:JSONCompatible]? - public func decode(key: String) throws -> [String: Value]? { + public func decode(_ key: String) throws -> [String: Value]? { guard let value = get(key) else { return nil } guard let dictionary = value as? [String: Value] else { - throw JSONDecodableError.IncompatibleTypeError(key: key, elementType: value.dynamicType, expectedType: [String: Value].self) + throw JSONDecodableError.incompatibleTypeError(key: key, elementType: type(of: value), expectedType: [String: Value].self) } return dictionary } // [String:JSONDecodable] - public func decode(key: String) throws -> [String: Element] { + public func decode(_ key: String) throws -> [String: Element] { guard let value = get(key) else { - throw JSONDecodableError.MissingTypeError(key: key) + throw JSONDecodableError.missingTypeError(key: key) } guard let dictionary = value as? [String: JSONObject] else { - throw JSONDecodableError.IncompatibleTypeError(key: key, elementType: value.dynamicType, expectedType: [String: Element].self) + throw JSONDecodableError.incompatibleTypeError(key: key, elementType: type(of: value), expectedType: [String: Element].self) } var decoded = [String: Element]() try dictionary.forEach { @@ -328,12 +329,12 @@ public class JSONDecoder { } // [String:JSONDecodable]? - public func decode(key: String) throws -> [String: Element]? { + public func decode(_ key: String) throws -> [String: Element]? { guard let value = get(key) else { return nil } guard let dictionary = value as? [String: JSONObject] else { - throw JSONDecodableError.IncompatibleTypeError(key: key, elementType: value.dynamicType, expectedType: [String: Element].self) + throw JSONDecodableError.incompatibleTypeError(key: key, elementType: type(of: value), expectedType: [String: Element].self) } var decoded = [String: Element]() try dictionary.forEach { @@ -343,29 +344,29 @@ public class JSONDecoder { } // JSONTransformable - public func decode(key: String, transformer: JSONTransformer) throws -> DecodedType { + public func decode(_ key: String, transformer: JSONTransformer) throws -> DecodedType { guard let value = get(key) else { - throw JSONDecodableError.MissingTypeError(key: key) + throw JSONDecodableError.missingTypeError(key: key) } guard let actual = value as? EncodedType else { - throw JSONDecodableError.IncompatibleTypeError(key: key, elementType: value.dynamicType, expectedType: EncodedType.self) + throw JSONDecodableError.incompatibleTypeError(key: key, elementType: type(of: value), expectedType: EncodedType.self) } guard let result = transformer.decoding(actual) else { - throw JSONDecodableError.TransformerFailedError(key: key) + throw JSONDecodableError.transformerFailedError(key: key) } return result } // JSONTransformable? - public func decode(key: String, transformer: JSONTransformer) throws -> DecodedType? { + public func decode(_ key: String, transformer: JSONTransformer) throws -> DecodedType? { guard let value = get(key) else { return nil } guard let actual = value as? EncodedType else { - throw JSONDecodableError.IncompatibleTypeError(key: key, elementType: value.dynamicType, expectedType: EncodedType.self) + throw JSONDecodableError.incompatibleTypeError(key: key, elementType: type(of: value), expectedType: EncodedType.self) } guard let result = transformer.decoding(actual) else { - throw JSONDecodableError.TransformerFailedError(key: key) + throw JSONDecodableError.transformerFailedError(key: key) } return result } diff --git a/JSONCodable/JSONEncodable+Mirror.swift b/JSONCodable/JSONEncodable+Mirror.swift index 3aef936..45d53eb 100644 --- a/JSONCodable/JSONEncodable+Mirror.swift +++ b/JSONCodable/JSONEncodable+Mirror.swift @@ -9,7 +9,7 @@ public extension Mirror { /** Builds an array of all properties from current class and all super classes - + - returns: array of Tuples containing the label and value for each property */ public func getAllProperties() -> [(label: String?, value: Any)] { @@ -18,8 +18,8 @@ public extension Mirror { children.append(element) } - children.appendContentsOf(self.superclassMirror()?.getAllProperties() ?? []) + children.append(contentsOf: self.superclassMirror?.getAllProperties() ?? []) return children } -} \ No newline at end of file +} diff --git a/JSONCodable/JSONEncodable.swift b/JSONCodable/JSONEncodable.swift index c2ef59b..d99258c 100644 --- a/JSONCodable/JSONEncodable.swift +++ b/JSONCodable/JSONEncodable.swift @@ -8,35 +8,35 @@ // Encoding Errors -public enum JSONEncodableError: ErrorProtocol, CustomStringConvertible { - case IncompatibleTypeError( +public enum JSONEncodableError: Error, CustomStringConvertible { + case incompatibleTypeError( elementType: Any.Type ) - case ArrayIncompatibleTypeError( + case arrayIncompatibleTypeError( elementType: Any.Type ) - case DictionaryIncompatibleTypeError( + case dictionaryIncompatibleTypeError( elementType: Any.Type ) - case ChildIncompatibleTypeError( + case childIncompatibleTypeError( key: String, elementType: Any.Type ) - case TransformerFailedError( + case transformerFailedError( key: String ) public var description: String { switch self { - case let .IncompatibleTypeError(elementType: elementType): + case let .incompatibleTypeError(elementType: elementType): return "JSONEncodableError: Incompatible type \(elementType)" - case let .ArrayIncompatibleTypeError(elementType: elementType): + case let .arrayIncompatibleTypeError(elementType: elementType): return "JSONEncodableError: Got an array of incompatible type \(elementType)" - case let .DictionaryIncompatibleTypeError(elementType: elementType): + case let .dictionaryIncompatibleTypeError(elementType: elementType): return "JSONEncodableError: Got an dictionary of incompatible type \(elementType)" - case let .ChildIncompatibleTypeError(key: key, elementType: elementType): + case let .childIncompatibleTypeError(key: key, elementType: elementType): return "JSONEncodableError: Got incompatible type \(elementType) for key \(key)" - case let .TransformerFailedError(key: key): + case let .transformerFailedError(key: key): return "JSONEncodableError: Transformer failed for key \(key)" } } @@ -45,26 +45,28 @@ public enum JSONEncodableError: ErrorProtocol, CustomStringConvertible { // Struct -> Dictionary public protocol JSONEncodable { - func toJSON() throws -> AnyObject + func toJSON() throws -> Any } public extension JSONEncodable { - func toJSON() throws -> AnyObject { + + func toJSON() throws -> Any { let mirror = Mirror(reflecting: self) #if !swift(>=3.0) - guard let style = mirror.displayStyle where style == .Struct || style == .Class else { + guard let style = mirror.displayStyle where style == .Struct || style == .Class else { throw JSONEncodableError.IncompatibleTypeError(elementType: self.dynamicType) - } + } #else - guard let style = mirror.displayStyle where style == .`struct` || style == .`class` else { - throw JSONEncodableError.IncompatibleTypeError(elementType: self.dynamicType) - } + + guard let style = mirror.displayStyle , style == .`struct` || style == .`class` else { + throw JSONEncodableError.incompatibleTypeError(elementType: type(of: self)) + } #endif - return try JSONEncoder.create({ (encoder) -> Void in + return try JSONEncoder.create { encoder in // loop through all properties (instance variables) - for (labelMaybe, valueMaybe) in mirror.getAllProperties() { + for (labelMaybe, valueMaybe) in mirror.children { guard let label = labelMaybe else { continue } @@ -90,24 +92,27 @@ public extension JSONEncodable { case let value as JSONDictionary: try encoder.encode(value, key: label) default: - throw JSONEncodableError.ChildIncompatibleTypeError(key: label, elementType: value.dynamicType) + throw JSONEncodableError.childIncompatibleTypeError(key: label, elementType: type(of: value)) } + } - }) + } } } -public extension Array {//where Element: JSONEncodable { + + +public extension Array { //where Element: JSONEncodable { private var wrapped: [Any] { return self.map{$0} } - public func toJSON() throws -> AnyObject { - var results: [AnyObject] = [] + public func toJSON() throws -> Any { + var results: [Any] = [] for item in self.wrapped { if let item = item as? JSONEncodable { results.append(try item.toJSON()) } else { - throw JSONEncodableError.ArrayIncompatibleTypeError(elementType: item.dynamicType) + throw JSONEncodableError.arrayIncompatibleTypeError(elementType: type(of: item)) } } return results @@ -117,14 +122,14 @@ public extension Array {//where Element: JSONEncodable { // Dictionary convenience methods public extension Dictionary {//where Key: String, Value: JSONEncodable { - public func toJSON() throws -> AnyObject { - var result: [String: AnyObject] = [:] + public func toJSON() throws -> Any { + var result: [String: Any] = [:] for (k, item) in self { if let item = item as? JSONEncodable { - result[String(k)] = try item.toJSON() + result[String(describing:k)] = try item.toJSON() } else { - throw JSONEncodableError.DictionaryIncompatibleTypeError(elementType: item.dynamicType) + throw JSONEncodableError.dictionaryIncompatibleTypeError(elementType: type(of: item)) } } return result @@ -136,50 +141,69 @@ public extension Dictionary {//where Key: String, Value: JSONEncodable { public class JSONEncoder { var object = JSONObject() - public static func create(@noescape setup: (encoder: JSONEncoder) throws -> Void) rethrows -> JSONObject { + public static func create(_ setup: (_ encoder: JSONEncoder) throws -> Void) rethrows -> JSONObject { let encoder = JSONEncoder() - try setup(encoder: encoder) + try setup(encoder) return encoder.object } - /* - Note: - There is some duplication because methods with generic constraints need to - take a concrete type conforming to the constraint are unable to take a parameter - typed to the protocol. Hence we need non-generic versions so we can cast from - Any to JSONEncodable in the default implementation for toJSON(). - */ + private func update(object: JSONObject, keys: [String], value: Any) -> JSONObject { + if keys.isEmpty { + return object + } + var newObject = object + var newKeys = keys + + let firstKey = newKeys.removeFirst() + + if newKeys.count > 0 { + let innerObject = object[firstKey] as? JSONObject ?? JSONObject() + newObject[firstKey] = update(object: innerObject, keys: newKeys, value: value) + } else { + newObject[firstKey] = value + } + return newObject + } + + /* + Note: + There is some duplication because methods with generic constraints need to + take a concrete type conforming to the constraint are unable to take a parameter + typed to the protocol. Hence we need non-generic versions so we can cast from + Any to JSONEncodable in the default implementation for toJSON(). + */ // JSONEncodable - public func encode(value: Encodable, key: String) throws { + public func encode(_ value: Encodable, key: String) throws { let result = try value.toJSON() - object[key] = result + object = update(object: object, keys: key.components(separatedBy: "."), value: result) } - private func encode(value: JSONEncodable, key: String) throws { + + fileprivate func encode(_ value: JSONEncodable, key: String) throws { let result = try value.toJSON() - object[key] = result + object = update(object: object, keys: key.components(separatedBy: "."), value: result) } - + // JSONEncodable? - public func encode(value: Encodable?, key: String) throws { + public func encode(_ value: JSONEncodable?, key: String) throws { guard let actual = value else { return } let result = try actual.toJSON() - object[key] = result + object = update(object: object, keys: key.components(separatedBy: "."), value: result) } - + // Enum - public func encode(value: Enum, key: String) throws { + public func encode(_ value: Enum, key: String) throws { guard let compatible = value.rawValue as? JSONCompatible else { return } let result = try compatible.toJSON() - object[key] = result + object = update(object: object, keys: key.components(separatedBy: "."), value: result) } // Enum? - public func encode(value: Enum?, key: String) throws { + public func encode(_ value: Enum?, key: String) throws { guard let actual = value else { return } @@ -187,35 +211,36 @@ public class JSONEncoder { return } let result = try compatible.toJSON() - object[key] = result + object = update(object: object, keys: key.components(separatedBy: "."), value: result) } // [JSONEncodable] - public func encode(array: [Encodable], key: String) throws { + public func encode(_ array: [Encodable], key: String) throws { guard array.count > 0 else { return } let result = try array.toJSON() - object[key] = result + object = update(object: object, keys: key.components(separatedBy: "."), value: result) } - public func encode(array: [JSONEncodable], key: String) throws { + public func encode(_ array: [JSONEncodable], key: String) throws { guard array.count > 0 else { return } let result = try array.toJSON() - object[key] = result + object = update(object: object, keys: key.components(separatedBy: "."), value: result) } - private func encode(array: JSONArray, key: String) throws { + + fileprivate func encode(_ array: JSONArray, key: String) throws { guard array.count > 0 && array.elementsAreJSONEncodable() else { return } let encodable = array.elementsMadeJSONEncodable() let result = try encodable.toJSON() - object[key] = result + object = update(object: object, keys: key.components(separatedBy: "."), value: result) } // [JSONEncodable]? - public func encode(value: [Encodable]?, key: String) throws { + public func encode(_ value: [Encodable]?, key: String) throws { guard let actual = value else { return } @@ -223,22 +248,22 @@ public class JSONEncoder { return } let result = try actual.toJSON() - object[key] = result + object = update(object: object, keys: key.components(separatedBy: "."), value: result) } // [Enum] - public func encode(value: [Enum], key: String) throws { + public func encode(_ value: [Enum], key: String) throws { guard value.count > 0 else { return } let result = try value.flatMap { try ($0.rawValue as? JSONCompatible)?.toJSON() } - object[key] = result + object = update(object: object, keys: key.components(separatedBy: "."), value: result) } // [Enum]? - public func encode(value: [Enum]?, key: String) throws { + public func encode(_ value: [Enum]?, key: String) throws { guard let actual = value else { return } @@ -248,35 +273,35 @@ public class JSONEncoder { let result = try actual.flatMap { try ($0.rawValue as? JSONCompatible)?.toJSON() } - object[key] = result + object = update(object: object, keys: key.components(separatedBy: "."), value: result) } // [String:JSONEncodable] - public func encode(dictionary: [String:Encodable], key: String) throws { + public func encode(_ dictionary: [String:Encodable], key: String) throws { guard dictionary.count > 0 else { return } let result = try dictionary.toJSON() - object[key] = result + object = update(object: object, keys: key.components(separatedBy: "."), value: result) } - public func encode(dictionary: [String:JSONEncodable], key: String) throws { + public func encode(_ dictionary: [String:JSONEncodable], key: String) throws { guard dictionary.count > 0 else { return } let result = try dictionary.toJSON() object[key] = result } - private func encode(dictionary: JSONDictionary, key: String) throws { + fileprivate func encode(_ dictionary: JSONDictionary, key: String) throws { guard dictionary.count > 0 && dictionary.valuesAreJSONEncodable() else { return } let encodable = dictionary.valuesMadeJSONEncodable() let result = try encodable.toJSON() - object[key] = result + object = update(object: object, keys: key.components(separatedBy: "."), value: result) } // [String:JSONEncodable]? - public func encode(value: [String:Encodable]?, key: String) throws { + public func encode(_ value: [String:Encodable]?, key: String) throws { guard let actual = value else { return } @@ -284,25 +309,25 @@ public class JSONEncoder { return } let result = try actual.toJSON() - object[key] = result + object = update(object: object, keys: key.components(separatedBy: "."), value: result) } // JSONTransformable - public func encode(value: DecodedType, key: String, transformer: JSONTransformer) throws { + public func encode(_ value: DecodedType, key: String, transformer: JSONTransformer) throws { guard let result = transformer.encoding(value) else { - throw JSONEncodableError.TransformerFailedError(key: key) + throw JSONEncodableError.transformerFailedError(key: key) } - object[key] = (result as! AnyObject) + object = update(object: object, keys: key.components(separatedBy: "."), value: result) } // JSONTransformable? - public func encode(value: DecodedType?, key: String, transformer: JSONTransformer) throws { + public func encode(_ value: DecodedType?, key: String, transformer: JSONTransformer) throws { guard let actual = value else { return } guard let result = transformer.encoding(actual) else { return } - object[key] = (result as! AnyObject) + object = update(object: object, keys: key.components(separatedBy: "."), value: result) } } diff --git a/JSONCodable/JSONHelpers.swift b/JSONCodable/JSONHelpers.swift index 1683d07..c5bfa40 100644 --- a/JSONCodable/JSONHelpers.swift +++ b/JSONCodable/JSONHelpers.swift @@ -8,54 +8,55 @@ // Convenience -public typealias JSONObject = [String: AnyObject] +public typealias JSONObject = [String: Any] // Dictionary handling protocol JSONDictionary { - var count: Int { get } - func valuesAreJSONEncodable() -> Bool - func valuesMadeJSONEncodable() -> [String: JSONEncodable] + var count: Int { get } + func valuesAreJSONEncodable() -> Bool + func valuesMadeJSONEncodable() -> [String: JSONEncodable] } extension Dictionary : JSONDictionary { - func valuesAreJSONEncodable() -> Bool { - return Key.self is String.Type && (Value.self is JSONEncodable.Type || Value.self is JSONEncodable.Protocol) - } - - func valuesMadeJSONEncodable() -> [String: JSONEncodable] { - var dict: [String: JSONEncodable] = [:] - for (k, v) in self { - dict[String(k)] = v as? JSONEncodable - } - return dict + func valuesAreJSONEncodable() -> Bool { + return Key.self is String.Type && (Value.self is JSONEncodable.Type || Value.self is JSONEncodable.Protocol) + } + + func valuesMadeJSONEncodable() -> [String: JSONEncodable] { + var dict: [String: JSONEncodable] = [:] + for (k, v) in self { + dict[String(describing:k)] = v as? JSONEncodable } + return dict + } } // Array handling protocol JSONArray { - var count: Int { get } - func elementsAreJSONEncodable() -> Bool - func elementsMadeJSONEncodable() -> [JSONEncodable] + var count: Int { get } + func elementsAreJSONEncodable() -> Bool + func elementsMadeJSONEncodable() -> [JSONEncodable] } extension Array: JSONArray { - func elementsAreJSONEncodable() -> Bool { - return Element.self is JSONEncodable.Type || Element.self is JSONEncodable.Protocol - } - - func elementsMadeJSONEncodable() -> [JSONEncodable] { - return self.map {$0 as! JSONEncodable} - } + func elementsAreJSONEncodable() -> Bool { + return Element.self is JSONEncodable.Type || Element.self is JSONEncodable.Protocol + } + + func elementsMadeJSONEncodable() -> [JSONEncodable] { + return self.map {$0 as! JSONEncodable} + } } // Optional handling protocol JSONOptional { - var wrapped: Any? { get } + var wrapped: Any? { get } } extension Optional: JSONOptional { - var wrapped: Any? { return self } + var wrapped: Any? { return self } } + diff --git a/JSONCodable/JSONString.swift b/JSONCodable/JSONString.swift index fa0da39..88ee3c8 100644 --- a/JSONCodable/JSONString.swift +++ b/JSONCodable/JSONString.swift @@ -14,23 +14,19 @@ public extension JSONEncodable { case let str as String: return escapeJSONString(str) case is Bool, is Int, is Float, is Double: - return String(self) + return String(describing:self) default: let json = try toJSON() - #if !swift(>=3.0) - let data = try NSJSONSerialization.dataWithJSONObject(json, options: NSJSONWritingOptions(rawValue: 0)) - #else - let data = try NSJSONSerialization.data(withJSONObject: json, options: NSJSONWritingOptions(rawValue: 0)) - #endif - guard let string = NSString(data: data, encoding: NSUTF8StringEncoding) else { + let data = try JSONSerialization.data(withJSONObject: json, options: []) + guard let string = String(data: data, encoding: String.Encoding.utf8) else { return "" } - return string as String + return string } } } -private func escapeJSONString(str: String) -> String { +private func escapeJSONString(_ str: String) -> String { var chars = String.CharacterView("\"") for c in str.characters { switch c { @@ -50,42 +46,25 @@ private func escapeJSONString(str: String) -> String { public extension Optional where Wrapped: JSONEncodable { public func toJSONString() throws -> String { - #if !swift(>=3.0) - switch self { - case let .Some(jsonEncodable): - return try jsonEncodable.toJSONString() - case nil: - return "null" - } - #else switch self { case let .some(jsonEncodable): return try jsonEncodable.toJSONString() case nil: return "null" } - #endif } } public extension JSONDecodable { init(JSONString: String) throws { - #if !swift(>=3.0) - guard let data = JSONString.dataUsingEncoding(NSUTF8StringEncoding) else { - throw JSONDecodableError.IncompatibleTypeError(key: "n/a", elementType: JSONString.dynamicType, expectedType: String.self) + guard let data = JSONString.data(using:String.Encoding.utf8) else { + throw JSONDecodableError.incompatibleTypeError(key: "n/a", elementType: String.self, expectedType: String.self) } - let result: AnyObject = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions(rawValue: 0)) - #else - guard let data = JSONString.data(usingEncoding:NSUTF8StringEncoding) else { - throw JSONDecodableError.IncompatibleTypeError(key: "n/a", elementType: JSONString.dynamicType, expectedType: String.self) - } + let result = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions(rawValue: 0)) - let result: AnyObject = try NSJSONSerialization.jsonObject(with: data, options: NSJSONReadingOptions(rawValue: 0)) - #endif - guard let converted = result as? [String: AnyObject] else { - throw JSONDecodableError.DictionaryTypeExpectedError(key: "n/a", elementType: result.dynamicType) + throw JSONDecodableError.dictionaryTypeExpectedError(key: "n/a", elementType: type(of: result)) } try self.init(object: converted) @@ -94,21 +73,14 @@ public extension JSONDecodable { public extension Array where Element: JSONDecodable { init(JSONString: String) throws { - #if !swift(>=3.0) - guard let data = JSONString.dataUsingEncoding(NSUTF8StringEncoding) else { - throw JSONDecodableError.IncompatibleTypeError(key: "n/a", elementType: JSONString.dynamicType, expectedType: String.self) + guard let data = JSONString.data(using: String.Encoding.utf8) else { + throw JSONDecodableError.incompatibleTypeError(key: "n/a", elementType: type(of: JSONString), expectedType: String.self) } - let result: AnyObject = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions(rawValue: 0)) - #else - guard let data = JSONString.data(usingEncoding: NSUTF8StringEncoding) else { - throw JSONDecodableError.IncompatibleTypeError(key: "n/a", elementType: JSONString.dynamicType, expectedType: String.self) - } + let result = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions(rawValue: 0)) - let result: AnyObject = try NSJSONSerialization.jsonObject(with: data, options: NSJSONReadingOptions(rawValue: 0)) - #endif guard let converted = result as? [AnyObject] else { - throw JSONDecodableError.ArrayTypeExpectedError(key: "n/a", elementType: result.dynamicType) + throw JSONDecodableError.arrayTypeExpectedError(key: "n/a", elementType: type(of: result)) } try self.init(JSONArray: converted) diff --git a/JSONCodable/JSONTransformer.swift b/JSONCodable/JSONTransformer.swift index c0804cd..c0b0f32 100644 --- a/JSONCodable/JSONTransformer.swift +++ b/JSONCodable/JSONTransformer.swift @@ -9,11 +9,11 @@ // Converting between types public struct JSONTransformer: CustomStringConvertible { - let decoding: (EncodedType -> DecodedType?) - let encoding: (DecodedType -> EncodedType?) + let decoding: ((EncodedType) -> DecodedType?) + let encoding: ((DecodedType) -> EncodedType?) - // needs public accessor - public init(decoding: (EncodedType -> DecodedType?), encoding: (DecodedType -> EncodedType?)) { + // needs public accessor + public init(decoding: @escaping ((EncodedType) -> DecodedType?), encoding: @escaping ((DecodedType) -> EncodedType?)) { self.decoding = decoding self.encoding = encoding } @@ -24,28 +24,22 @@ public struct JSONTransformer: CustomStringConvertible } import Foundation -private let dateTimeFormatter: NSDateFormatter = { - let formatter = NSDateFormatter() +private let dateTimeFormatter: DateFormatter = { + let formatter = DateFormatter() formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" - formatter.timeZone = NSTimeZone(forSecondsFromGMT: 0) - formatter.locale = NSLocale(localeIdentifier: "en_US_POSIX") + formatter.timeZone = TimeZone(secondsFromGMT:0) + formatter.locale = Locale(identifier: "en_US_POSIX") return formatter }() public struct JSONTransformers { - public static let StringToNSURL = JSONTransformer( - decoding: {NSURL(string: $0)}, + public static let StringToURL = JSONTransformer( + decoding: {URL(string: $0)}, encoding: {$0.absoluteString}) - - #if !swift(>=3.0) - public static let StringToNSDate = JSONTransformer( - decoding: {dateTimeFormatter.dateFromString($0)}, - encoding: {dateTimeFormatter.stringFromDate($0)}) - #else - public static let StringToNSDate = JSONTransformer( + + public static let StringToDate = JSONTransformer( decoding: {dateTimeFormatter.date(from: $0)}, encoding: {dateTimeFormatter.string(from: $0)}) - #endif } public extension JSONTransformer { diff --git a/JSONCodableTests/ClassInheritance.swift b/JSONCodableTests/ClassInheritance.swift index 6f9cd81..fc97b4b 100644 --- a/JSONCodableTests/ClassInheritance.swift +++ b/JSONCodableTests/ClassInheritance.swift @@ -24,4 +24,4 @@ class Grandchild : Child { var grandChildProperty1:String = "grandChild1" var grandChildProperty2:String = "grandChild2" -} \ No newline at end of file +} diff --git a/JSONCodableTests/Company.swift b/JSONCodableTests/Company.swift index 6d040ed..5597721 100644 --- a/JSONCodableTests/Company.swift +++ b/JSONCodableTests/Company.swift @@ -6,7 +6,6 @@ // // -import Foundation import JSONCodable struct Company: Equatable { @@ -26,4 +25,4 @@ extension Company: JSONDecodable { name = try decoder.decode("name") address = try decoder.decode("address") } -} \ No newline at end of file +} diff --git a/JSONCodableTests/EncodeNestingTests.swift b/JSONCodableTests/EncodeNestingTests.swift new file mode 100644 index 0000000..3c5e66c --- /dev/null +++ b/JSONCodableTests/EncodeNestingTests.swift @@ -0,0 +1,33 @@ +// +// EncodeNestingTests.swift +// JSONCodable +// +// Created by Richard Fox on 6/19/16. +// +// + +import XCTest +import JSONCodable + +class EncodeNestingTests: XCTestCase { + + let propertyItemArray: JSONObject = [ + "class": "propertyType", + "rel": "propertyType", + "properties": + [ "name": "person", + "location": [ "coord": [ + "lat": 37.790770, + "long": -122.402015 + ]]]] + + func testEncodeNestedPropertyItem() { + guard let pItem = try? PropertyItem(object: propertyItemArray), + let json = try? pItem.toJSON(), + let json1 = json as? JSONObject else { + XCTFail() + return + } + XCTAssert(String(describing:json1) == String(describing:propertyItemArray), "failed to convert to \(propertyItemArray)") + } +} diff --git a/JSONCodableTests/EnumTests.swift b/JSONCodableTests/EnumTests.swift index f8d0f4d..e805c24 100644 --- a/JSONCodableTests/EnumTests.swift +++ b/JSONCodableTests/EnumTests.swift @@ -13,7 +13,7 @@ class EnumTests: XCTestCase { let encodedValue = ["name": "apple", "color": "Red"] let decodedValue = Fruit(name: "apple", color: FruitColor.Red) - let encodedValue2 = ["name": "Seaweed Pasta", "cuisines": ["Italian", "Japanese"]] + let encodedValue2: [String: Any] = ["name": "Seaweed Pasta", "cuisines": ["Italian", "Japanese"]] let decodedValue2 = Food(name: "Seaweed Pasta", cuisines: [.Italian, .Japanese]) func testDecodingEnum() { diff --git a/JSONCodableTests/Food.swift b/JSONCodableTests/Food.swift index 14847d1..91a5084 100644 --- a/JSONCodableTests/Food.swift +++ b/JSONCodableTests/Food.swift @@ -6,7 +6,6 @@ // // -import Foundation import JSONCodable enum Cuisine: String { @@ -38,10 +37,10 @@ extension Food: JSONCodable { cuisines = try decoder.decode("cuisines") } - func toJSON() throws -> AnyObject { + func toJSON() throws -> Any { return try JSONEncoder.create({ (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 3d981bc..dcda939 100644 --- a/JSONCodableTests/Fruit.swift +++ b/JSONCodableTests/Fruit.swift @@ -6,34 +6,33 @@ // // -import Foundation import JSONCodable enum FruitColor: String { - case Red - case Blue + case Red + case Blue } struct Fruit: Equatable { - let name: String - let color: FruitColor + let name: String + let color: FruitColor } func ==(lhs: Fruit, rhs: Fruit) -> Bool { - return lhs.name == rhs.name && lhs.color == rhs.color + return lhs.name == rhs.name && lhs.color == rhs.color } extension Fruit: JSONCodable { - init(object: JSONObject) throws { - let decoder = JSONDecoder(object: object) - name = try decoder.decode("name") - color = try decoder.decode("color") - } - - func toJSON() throws -> AnyObject { - return try JSONEncoder.create({ (encoder) -> Void in - try encoder.encode(name, key: "name") - try encoder.encode(color, key: "color") - }) - } -} \ No newline at end of file + init(object: JSONObject) throws { + let decoder = JSONDecoder(object: object) + name = try decoder.decode("name") + color = try decoder.decode("color") + } + + func toJSON() throws -> Any { + return try JSONEncoder.create({ (encoder) -> Void in + try encoder.encode(name, key: "name") + try encoder.encode(color, key: "color") + }) + } +} diff --git a/JSONCodableTests/HelperTests.swift b/JSONCodableTests/HelperTests.swift index 5bfa0db..5ccffce 100644 --- a/JSONCodableTests/HelperTests.swift +++ b/JSONCodableTests/HelperTests.swift @@ -15,35 +15,35 @@ struct NotEncodable { class HelperTests: XCTestCase { func testArrayElementsAreEncodable() { - let intArray:[Int] = [1,2,3] - XCTAssert(intArray.elementsAreJSONEncodable(), "Array of type [Int] should be encodable") - - let encodableArray:[JSONEncodable] = [1,2,3] - XCTAssert(encodableArray.elementsAreJSONEncodable(), "Array of type [JSONEncodable] should be encodable") - - let notEncodableArray:[NotEncodable] = [NotEncodable()] - XCTAssert(!notEncodableArray.elementsAreJSONEncodable(), "Array of type [NotEncodable] should not be encodable") - - let _ = try? JSONEncoder.create({ (encoder) -> Void in - try encoder.encode(intArray, key: "intArray") - try encoder.encode(encodableArray, key: "encodableArray") - }) - } - - func testDictionaryIsEncodable() { - let intDict:[String:Int] = ["a":1,"b":2,"c":3] - XCTAssert(intDict.valuesAreJSONEncodable(), "Dictionary of type [String:Int] should be encodable") - - let encodableDict:[String:JSONEncodable] = ["a":1,"b":2,"c":3] - XCTAssert(encodableDict.valuesAreJSONEncodable(), "Dictionary of type [String:JSONEncodable] should be encodable") - - 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 - try encoder.encode(intDict, key: "intArray") - try encoder.encode(encodableDict, key: "encodableArray") - }) - } + let intArray:[Int] = [1,2,3] + XCTAssert(intArray.elementsAreJSONEncodable(), "Array of type [Int] should be encodable") + + let encodableArray:[JSONEncodable] = [1,2,3] + XCTAssert(encodableArray.elementsAreJSONEncodable(), "Array of type [JSONEncodable] should be encodable") + + let notEncodableArray:[NotEncodable] = [NotEncodable()] + XCTAssert(!notEncodableArray.elementsAreJSONEncodable(), "Array of type [NotEncodable] should not be encodable") + + let _ = try? JSONEncoder.create({ (encoder) -> Void in + try encoder.encode(intArray, key: "intArray") + try encoder.encode(encodableArray, key: "encodableArray") + }) + } + + func testDictionaryIsEncodable() { + let intDict:[String:Int] = ["a":1,"b":2,"c":3] + XCTAssert(intDict.valuesAreJSONEncodable(), "Dictionary of type [String:Int] should be encodable") + + let encodableDict:[String:JSONEncodable] = ["a":1,"b":2,"c":3] + XCTAssert(encodableDict.valuesAreJSONEncodable(), "Dictionary of type [String:JSONEncodable] should be encodable") + + 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 + try encoder.encode(intDict, key: "intArray") + try encoder.encode(encodableDict, key: "encodableArray") + }) + } } diff --git a/JSONCodableTests/ImageAsset.swift b/JSONCodableTests/ImageAsset.swift index 5561b6c..09953c5 100644 --- a/JSONCodableTests/ImageAsset.swift +++ b/JSONCodableTests/ImageAsset.swift @@ -6,31 +6,30 @@ // // -import Foundation import JSONCodable struct ImageAsset: Equatable { - let name: String - var uri: NSURL? + let name: String + var uri: URL? } func ==(lhs: ImageAsset, rhs: ImageAsset) -> Bool { - return lhs.name == rhs.name && lhs.uri == rhs.uri + return lhs.name == rhs.name && lhs.uri == rhs.uri } extension ImageAsset: JSONEncodable { - func toJSON() throws -> AnyObject { - return try JSONEncoder.create({ (encoder) -> Void in - try encoder.encode(name, key: "name") - try encoder.encode(uri, key: "uri", transformer: JSONTransformers.StringToNSURL) - }) + func toJSON() throws -> Any { + return try JSONEncoder.create{ (encoder) -> Void in + try encoder.encode(name, key: "name") + try encoder.encode(uri, key: "uri", transformer: JSONTransformers.StringToURL) } + } } extension ImageAsset: JSONDecodable { - init(object: JSONObject) throws { - let decoder = JSONDecoder(object: object) - name = try decoder.decode("name") - uri = try decoder.decode("uri", transformer: JSONTransformers.StringToNSURL) - } -} \ No newline at end of file + init(object: JSONObject) throws { + let decoder = JSONDecoder(object: object) + name = try decoder.decode("name") + uri = try decoder.decode("uri", transformer: JSONTransformers.StringToURL) + } +} diff --git a/JSONCodableTests/Messages.swift b/JSONCodableTests/Messages.swift new file mode 100644 index 0000000..a251c21 --- /dev/null +++ b/JSONCodableTests/Messages.swift @@ -0,0 +1,46 @@ +// +// Messages.swift +// JSONCodable +// +// Created by Richard Fox on 6/25/16. +// +// + +import JSONCodable + +struct Messages { + let id: [JSONObject] +} + +extension Messages: JSONDecodable { + init(object: JSONObject) throws { + let decoder = JSONDecoder(object: object) + id = try decoder.decode("", transformer: JSONTransformers.JSONObjectArray) + } +} + +struct MessageComplex { + let id: JSONObject + let nestedId: Int +} + +extension MessageComplex: JSONDecodable { + init(object: JSONObject) throws { + let decoder = JSONDecoder(object: object) + id = try decoder.decode("[0]", transformer: JSONTransformers.JSONObjectType) + nestedId = try decoder.decode("[1].ID") + } +} + +//Transforms for returning JSONObject & [JSONObject] + +extension JSONTransformers { + + static let JSONObjectType = JSONTransformer( + decoding: { $0 }, + encoding: { $0 }) + + static let JSONObjectArray = JSONTransformer<[JSONObject],[JSONObject]>( + decoding: { $0 }, + encoding: { $0 }) +} diff --git a/JSONCodableTests/NestItem.swift b/JSONCodableTests/NestItem.swift index 40ff086..c94d9bf 100644 --- a/JSONCodableTests/NestItem.swift +++ b/JSONCodableTests/NestItem.swift @@ -6,11 +6,10 @@ // // -import Foundation import JSONCodable struct NestItem { - let areas: [[Float]] + let areas: [[Double]] var places: [[String]]? var business: [[Company]] var assets: [[ImageAsset]]? @@ -28,6 +27,4 @@ extension NestItem: JSONDecodable { fatalError("\(error)") } } - } - diff --git a/JSONCodableTests/PropertyItem.swift b/JSONCodableTests/PropertyItem.swift new file mode 100644 index 0000000..26b29e6 --- /dev/null +++ b/JSONCodableTests/PropertyItem.swift @@ -0,0 +1,44 @@ +// +// PropertyItem.swift +// JSONCodable +// +// Created by Richard Fox on 6/19/16. +// +// + +import JSONCodable + +struct PropertyItem { + let name: String + let long: Double + let lat: Double + let rel: String + let type: String +} + +extension PropertyItem: JSONDecodable { + init(object: JSONObject) throws { + do { + let decoder = JSONDecoder(object: object) + rel = try decoder.decode("rel") + type = try decoder.decode("class") + name = try decoder.decode("properties.name") + long = try decoder.decode("properties.location.coord.long") + lat = try decoder.decode("properties.location.coord.lat") + }catch{ + fatalError("\(error)") + } + } +} + +extension PropertyItem: JSONEncodable { + func toJSON() throws -> Any { + return try JSONEncoder.create { (encoder) -> Void in + try encoder.encode(rel, key: "rel") + try encoder.encode(type, key: "class") + try encoder.encode(name, key: "properties.name") + try encoder.encode(long, key: "properties.location.coord.long") + try encoder.encode(lat, key: "properties.location.coord.lat") + } + } +} diff --git a/JSONCodableTests/RegularTests.swift b/JSONCodableTests/RegularTests.swift index 37e3b67..6e35428 100644 --- a/JSONCodableTests/RegularTests.swift +++ b/JSONCodableTests/RegularTests.swift @@ -8,28 +8,30 @@ import XCTest -class RegularTests: XCTestCase { - let nestedCodableArray = ["areas" : [[10.0,10.5,12.5]], - "places":[["Tokyo","New York", "El Cerrito"]], - "business" : [[ - [ "name": "Apple", - "address": "1 Infinite Loop, Cupertino, CA" - ], - [ "name": "Propeller", - "address": "1212 broadway, Oakland, CA" - ] - ]], - "assets": [[ - [ "name": "image-name", - "uri": "http://www.example.com/image.png" - ], - [ "name": "image2-name", - "uri": "http://www.example.com/image2.png" - ] - ]]] +class RegularTests: XCTestCase { + + let nestedCodableArray: [String: Any] = [ + "areas" : [[10.0,10.5,12.5]], + "places": [["Tokyo","New York", "El Cerrito"]], + "business" : [[ + [ "name": "Apple", + "address": "1 Infinite Loop, Cupertino, CA" + ], + [ "name": "Propeller", + "address": "1212 broadway, Oakland, CA" + ] + ]], + "assets": [[ + [ "name": "image-name", + "uri": "http://www.example.com/image.png" + ], + [ "name": "image2-name", + "uri": "http://www.example.com/image2.png" + ] + ]]] - let encodedNestedArray = [ + let encodedNestedArray: [String : Any] = [ "id": 99, "full_name": "Jen Jackson", "properties":[ @@ -39,7 +41,7 @@ class RegularTests: XCTestCase { ] ] - let encodedValue = [ + let encodedValue: [String: Any] = [ "id": 24, "full_name": "John Appleseed", "email": "john@appleseed.com", @@ -65,7 +67,7 @@ class RegularTests: XCTestCase { ], friendsLookup: ["Bob Jefferson": User(id: 27, likes:0, name: "Bob Jefferson", email: nil, company: nil, friends: [], friendsLookup: nil)] ) - + func testDecodeNestedCodableArray() { guard let nested = try? NestItem(object: nestedCodableArray) else { XCTFail() @@ -75,21 +77,22 @@ class RegularTests: XCTestCase { let places = nested.places ?? [[]] let areas = nested.areas let business = nested.business - let assets = nested.assets ?? [[]] - XCTAssert(places == [["Tokyo","New York", "El Cerrito"]], "\(nestedCodableArray))") - XCTAssert(areas == [[10.0,10.5,12.5]], "\(nestedCodableArray))") - - XCTAssert(business.map{ $0.map{ $0.name } } == [[try! Company(object:["name": "Apple", - "address": "1 Infinite Loop, Cupertino, CA"]), - try! Company(object:[ "name": "Propeller", - "address": "1212 broadway, Oakland, CA"])].map{ $0.name }], - "\(nestedCodableArray))") - - XCTAssert(assets.map{ $0.map{ $0.name } } == [[try! ImageAsset(object:[ "name": "image-name", - "uri": "http://www.example.com/image.png"]), - try! ImageAsset(object: ["name": "image2-name", - "uri": "http://www.example.com/image2.png"])].map{ $0.name }], - "\(nestedCodableArray))") + let assets = nested.assets ?? [[]] + + XCTAssert(places as NSObject == [["Tokyo","New York", "El Cerrito"]] as NSObject, "\(nestedCodableArray))") + XCTAssert(areas as NSObject == [[10.0,10.5,12.5]] as NSObject, "\(nestedCodableArray))") + + XCTAssert(business.map{ $0.map{ $0.name } } as NSObject == [[try! Company(object:["name": "Apple", + "address": "1 Infinite Loop, Cupertino, CA"]), + try! Company(object:[ "name": "Propeller", + "address": "1212 broadway, Oakland, CA"])].map{ $0.name }] as NSObject, + "\(nestedCodableArray))") + + XCTAssert(assets.map{ $0.map{ $0.name } } as NSObject == [[try! ImageAsset(object:[ "name": "image-name", + "uri": "http://www.example.com/image.png"]), + try! ImageAsset(object: ["name": "image2-name", + "uri": "http://www.example.com/image2.png"])].map{ $0.name }] as NSObject, + "\(nestedCodableArray))") } func testDecodingNestedArray() { @@ -107,13 +110,14 @@ class RegularTests: XCTestCase { } XCTAssertEqual(user, decodedValue) } - + func testEncodingRegular() { - guard let json = try? decodedValue.toJSON() else { + guard let json = (try? decodedValue.toJSON()) as? NSDictionary else { XCTFail() return } - XCTAssertEqual(json as! [String : NSObject], encodedValue) + XCTAssert(json == (encodedValue as NSDictionary)) + } } diff --git a/JSONCodableTests/TransformerTests.swift b/JSONCodableTests/TransformerTests.swift index f323923..fc523bc 100644 --- a/JSONCodableTests/TransformerTests.swift +++ b/JSONCodableTests/TransformerTests.swift @@ -7,16 +7,45 @@ // import XCTest +import JSONCodable class TransformerTests: XCTestCase { - let encodedValue = [ + let testMessageJSON: [JSONObject] = [ + [ + "ID": 568, + "av": 125435865, + "ad": "2016-06-07", + "ar": 0, + "at": 0, + "ah": 0, + "aj": "te" + ] + ] + + let testMessageComplexJSON: [JSONObject] = [ + [ + "ID": 568, + "av": 125435865, + "ad": "2016-06-07", + "ar": 0, + "at": 0, + "ah": 0, + "aj": "te" + ], + [ + "ID": 415 + ] + ] + + let encodedValue: [String: String] = [ "name": "image-name", "uri": "http://www.example.com/image.png" ] + let decodedValue = ImageAsset( name: "image-name", - uri: NSURL(string: "http://www.example.com/image.png") + uri: URL(string: "http://www.example.com/image.png") ) func testDecodingTransformer() { @@ -33,8 +62,29 @@ class TransformerTests: XCTestCase { XCTFail() return } - - XCTAssertEqual(json as! [String : NSObject], encodedValue) + XCTAssertEqual(json as! [String : String], encodedValue) } -} \ No newline at end of file + func testTranformMessagesArrayOfJSONObject() { + guard let messageIds = try? Messages.init(object: testMessageJSON).id else { + XCTAssert(false, "could not create Messages object") + return + } + XCTAssert(messageIds as NSObject == testMessageJSON as NSObject, + "message Id were not converted to Messages type correcrtly") + + guard let messageComplexIds = try? MessageComplex.init(object: testMessageComplexJSON).id else { + XCTAssert(false, "could not create MessageComplex object") + return + } + guard let messageComplexNestedId = try? MessageComplex.init(object: testMessageComplexJSON).nestedId else { + XCTAssert(false, "could not create MessageComplex object") + return + } + XCTAssert(String(describing: messageComplexIds) == String(describing: testMessageComplexJSON[0]), + "message Ids were not converted to MessageComplex type Ids property correcrtly") + + XCTAssert(String(messageComplexNestedId) == String(describing: testMessageComplexJSON[1]["ID"]!), + "item from [1][ID] was not converted to MessageComplex type nestedId property correcrtly") + } +} diff --git a/JSONCodableTests/User.swift b/JSONCodableTests/User.swift index b2dafaf..b810c90 100644 --- a/JSONCodableTests/User.swift +++ b/JSONCodableTests/User.swift @@ -6,7 +6,6 @@ // // -import Foundation import JSONCodable struct User: Equatable { @@ -28,21 +27,21 @@ func ==(lhs: User, rhs: User) -> Bool { } extension User: JSONEncodable { - func toJSON() throws -> AnyObject { - return try JSONEncoder.create({ (encoder) -> Void in + func toJSON() throws -> Any { + return try JSONEncoder.create { (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") try encoder.encode(friendsLookup, key: "friendsLookup") - }) + } } } extension User: JSONDecodable { init(object: JSONObject) throws { - let decoder = JSONDecoder(object: object) + let decoder = JSONDecoder(object: object) id = try decoder.decode("id") likes = try decoder.decode("properties[0].likes") name = try decoder.decode("full_name") @@ -51,4 +50,4 @@ extension User: JSONDecodable { friends = try decoder.decode("friends") friendsLookup = try decoder.decode("friendsLookup") } -} \ No newline at end of file +} diff --git a/JSONCodableTests/UtilityTests.swift b/JSONCodableTests/UtilityTests.swift index ef63fd4..e32af93 100644 --- a/JSONCodableTests/UtilityTests.swift +++ b/JSONCodableTests/UtilityTests.swift @@ -24,4 +24,4 @@ class UtilityTests: XCTestCase { XCTAssert(decoder.parseArrayIndex("HAHA") == nil) XCTAssert(decoder.parseArrayIndex("[-1]") == -1) } -} \ No newline at end of file +} From 1610357322c6e00294e8b5acef27d4ea3d6ddbec Mon Sep 17 00:00:00 2001 From: Nadohs Date: Thu, 22 Sep 2016 20:35:27 -0700 Subject: [PATCH 3/9] update podspec version to 3.0 --- JSONCodable.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/JSONCodable.podspec b/JSONCodable.podspec index 5c49cc9..3788ae4 100644 --- a/JSONCodable.podspec +++ b/JSONCodable.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'JSONCodable' - s.version = '2.2' + s.version = '3.0' s.ios.deployment_target = '8.0' s.osx.deployment_target = '10.10' s.license = { :type => 'MIT', :file => 'LICENSE' } From 12b28475e81cb9dab970cbc3e01d8dbe6d0b4b0e Mon Sep 17 00:00:00 2001 From: Nadohs Date: Thu, 22 Sep 2016 21:14:25 -0700 Subject: [PATCH 4/9] fixes for release: 1) remove JSONTransformers missing references `StringToNSURL` and `StringToNSDate` 2) add test from `develop2` branch for array of JSONEncodable initializer extension --- JSONCodable/JSONTransformer.swift | 10 ---------- JSONCodableTests/RegularTests.swift | 12 +++++++++++- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/JSONCodable/JSONTransformer.swift b/JSONCodable/JSONTransformer.swift index c0b0f32..a5346d0 100644 --- a/JSONCodable/JSONTransformer.swift +++ b/JSONCodable/JSONTransformer.swift @@ -41,13 +41,3 @@ public struct JSONTransformers { decoding: {dateTimeFormatter.date(from: $0)}, encoding: {dateTimeFormatter.string(from: $0)}) } - -public extension JSONTransformer { - static var stringToURL: JSONTransformer{ - return JSONTransformers.StringToNSURL - } - - static var stringToDate: JSONTransformer{ - return JSONTransformers.StringToNSDate - } -} diff --git a/JSONCodableTests/RegularTests.swift b/JSONCodableTests/RegularTests.swift index 6e35428..8a3982e 100644 --- a/JSONCodableTests/RegularTests.swift +++ b/JSONCodableTests/RegularTests.swift @@ -67,7 +67,17 @@ class RegularTests: XCTestCase { ], friendsLookup: ["Bob Jefferson": User(id: 27, likes:0, name: "Bob Jefferson", email: nil, company: nil, friends: [], friendsLookup: nil)] ) - + + func testArrayOfUsers() { + let userArray = [encodedValue, encodedValue] + guard let users = try? [User](JSONArray: userArray) else { + XCTFail() + return + } + XCTAssertEqual(users[0], decodedValue) + XCTAssertEqual(users[1], decodedValue) + } + func testDecodeNestedCodableArray() { guard let nested = try? NestItem(object: nestedCodableArray) else { XCTFail() From c7e27b977f7a063250c8c771c8319dd0335965f0 Mon Sep 17 00:00:00 2001 From: Lukas Schmidt Date: Thu, 6 Oct 2016 07:41:30 +0200 Subject: [PATCH 5/9] Updates version number for dependency management tools (#56) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2784525..b0c46c3 100644 --- a/README.md +++ b/README.md @@ -24,12 +24,12 @@ Hassle-free JSON encoding and decoding in Swift - Simply add the following to your [`Cartfile`](https://github.com/Carthage/Carthage) and run `carthage update`: ``` -github "matthewcheok/JSONCodable" ~> 2.1 +github "matthewcheok/JSONCodable" ~> 3.0 ``` - or add the following to your [`Podfile`](http://cocoapods.org/) and run `pod install`: ``` -pod 'JSONCodable', '~> 2.1' +pod 'JSONCodable', '~> 3.0' ``` - or clone as a git submodule, From 1f1d4cd13dab54aac6030c1382320d2cf0b529bc Mon Sep 17 00:00:00 2001 From: Jonathan Sibley Date: Thu, 27 Oct 2016 21:50:18 -0700 Subject: [PATCH 6/9] Explicitly coerce to Any (eliminate Swift 3.0.1 compiler warning) (#57) --- JSONCodable/JSONDecodable.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/JSONCodable/JSONDecodable.swift b/JSONCodable/JSONDecodable.swift index 46cf0ba..3cb8095 100644 --- a/JSONCodable/JSONDecodable.swift +++ b/JSONCodable/JSONDecodable.swift @@ -139,7 +139,7 @@ public class JSONDecoder { // JSONCompatible? public func decode(_ key: String) throws -> Compatible? { - return (get(key) ?? object[key]) as? Compatible + return (get(key) ?? object[key] as Any) as? Compatible } // JSONDecodable From 23de3e9bc4052239f29fc15229dfd1e80ca52b7d Mon Sep 17 00:00:00 2001 From: Nadohs Date: Sun, 6 Nov 2016 15:55:13 -0800 Subject: [PATCH 7/9] update podspec and project settings --- JSONCodable.podspec | 2 +- JSONCodable.xcodeproj/project.pbxproj | 10 +++++++--- .../xcshareddata/xcschemes/JSONCodable OSX.xcscheme | 2 +- .../xcshareddata/xcschemes/JSONCodable iOS.xcscheme | 2 +- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/JSONCodable.podspec b/JSONCodable.podspec index 3788ae4..86f8a5e 100644 --- a/JSONCodable.podspec +++ b/JSONCodable.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'JSONCodable' - s.version = '3.0' + s.version = '3.0.1' s.ios.deployment_target = '8.0' s.osx.deployment_target = '10.10' s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/JSONCodable.xcodeproj/project.pbxproj b/JSONCodable.xcodeproj/project.pbxproj index 7e2df37..0eda2e6 100644 --- a/JSONCodable.xcodeproj/project.pbxproj +++ b/JSONCodable.xcodeproj/project.pbxproj @@ -275,7 +275,7 @@ 9EDF80101B59CFCE00E4A2D6 /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0800; + LastUpgradeCheck = 0810; TargetAttributes = { 9E455BF61BCE185B00070A4F = { CreatedOnToolsVersion = 7.0.1; @@ -503,7 +503,7 @@ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = dwarf; @@ -563,7 +563,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "iPhone Distribution"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; @@ -717,7 +717,9 @@ CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -742,7 +744,9 @@ CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; diff --git a/JSONCodable.xcodeproj/xcshareddata/xcschemes/JSONCodable OSX.xcscheme b/JSONCodable.xcodeproj/xcshareddata/xcschemes/JSONCodable OSX.xcscheme index 6156403..be69527 100644 --- a/JSONCodable.xcodeproj/xcshareddata/xcschemes/JSONCodable OSX.xcscheme +++ b/JSONCodable.xcodeproj/xcshareddata/xcschemes/JSONCodable OSX.xcscheme @@ -1,6 +1,6 @@ Date: Mon, 7 Nov 2016 21:16:24 -0800 Subject: [PATCH 8/9] update version numbers in README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b0c46c3..cba46bc 100644 --- a/README.md +++ b/README.md @@ -24,12 +24,12 @@ Hassle-free JSON encoding and decoding in Swift - Simply add the following to your [`Cartfile`](https://github.com/Carthage/Carthage) and run `carthage update`: ``` -github "matthewcheok/JSONCodable" ~> 3.0 +github "matthewcheok/JSONCodable" ~> 3.0.1 ``` - or add the following to your [`Podfile`](http://cocoapods.org/) and run `pod install`: ``` -pod 'JSONCodable', '~> 3.0' +pod 'JSONCodable', '~> 3.0.1' ``` - or clone as a git submodule, From ba15212542076796db5b54054a4bd7de0df1c677 Mon Sep 17 00:00:00 2001 From: Nadohs Date: Thu, 10 Nov 2016 21:54:00 -0800 Subject: [PATCH 9/9] Mixed array (#59) * add `filter` option on JSONDecodable `decode` methods that return array types add `filter` option to array initializer - these options allow mapping over JSON array content to no longer fail when JSON objects in array are not all of same kind. * fix warnings * fix file merge --- JSONCodable.xcodeproj/project.pbxproj | 8 +++ JSONCodable/JSONDecodable.swift | 51 +++++++++++----- JSONCodableTests/ArrayTests.swift | 85 ++++++++++++++++++++++++++ JSONCodableTests/EnumTests.swift | 2 +- JSONCodableTests/PropertyCompany.swift | 24 ++++++++ JSONCodableTests/PropertyItem.swift | 16 ++--- 6 files changed, 161 insertions(+), 25 deletions(-) create mode 100644 JSONCodableTests/ArrayTests.swift create mode 100644 JSONCodableTests/PropertyCompany.swift diff --git a/JSONCodable.xcodeproj/project.pbxproj b/JSONCodable.xcodeproj/project.pbxproj index 0eda2e6..f373ee8 100644 --- a/JSONCodable.xcodeproj/project.pbxproj +++ b/JSONCodable.xcodeproj/project.pbxproj @@ -7,6 +7,8 @@ objects = { /* Begin PBXBuildFile section */ + 520B867D1DCEA60900885504 /* ArrayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520B867C1DCEA60900885504 /* ArrayTests.swift */; }; + 520B867F1DCEB6A300885504 /* PropertyCompany.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520B867E1DCEB6A300885504 /* PropertyCompany.swift */; }; 5211CD0A1CE2EBFB0097F255 /* NestItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5211CD091CE2EBFB0097F255 /* NestItem.swift */; }; 52E8F44F1C9087D200F40F7F /* UtilityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52E8F44E1C9087D200F40F7F /* UtilityTests.swift */; }; 9E455BFA1BCE185B00070A4F /* EnumTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E455BF91BCE185B00070A4F /* EnumTests.swift */; }; @@ -53,6 +55,8 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 520B867C1DCEA60900885504 /* ArrayTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArrayTests.swift; sourceTree = ""; }; + 520B867E1DCEB6A300885504 /* PropertyCompany.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PropertyCompany.swift; sourceTree = ""; }; 5211CD091CE2EBFB0097F255 /* NestItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NestItem.swift; sourceTree = ""; }; 52E8F44E1C9087D200F40F7F /* UtilityTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UtilityTests.swift; sourceTree = ""; }; 9E455BF71BCE185B00070A4F /* JSONCodableTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = JSONCodableTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -124,6 +128,7 @@ isa = PBXGroup; children = ( 5211CD091CE2EBFB0097F255 /* NestItem.swift */, + 520B867E1DCEB6A300885504 /* PropertyCompany.swift */, BDD667CB1D1F3572003F94D7 /* Messages.swift */, BD885BBF1D173A0700CA767A /* PropertyItem.swift */, 9E455C021BCE1C1E00070A4F /* Fruit.swift */, @@ -140,6 +145,7 @@ isa = PBXGroup; children = ( 9ECF00C31BCF82F5008D557C /* HelperTests.swift */, + 520B867C1DCEA60900885504 /* ArrayTests.swift */, 52E8F44E1C9087D200F40F7F /* UtilityTests.swift */, 9E455C0A1BCE1F0100070A4F /* RegularTests.swift */, BD885BBD1D17358E00CA767A /* EncodeNestingTests.swift */, @@ -343,11 +349,13 @@ A1B71C7E1D37E90B006DA33A /* MirrorTests.swift in Sources */, 9ECF00C41BCF82F5008D557C /* HelperTests.swift in Sources */, 9ECF00C21BCF6E43008D557C /* ImageAsset.swift in Sources */, + 520B867D1DCEA60900885504 /* ArrayTests.swift in Sources */, 9E455C031BCE1C1E00070A4F /* Fruit.swift in Sources */, 9ECF00C01BCE251B008D557C /* TransformerTests.swift in Sources */, BDD667CC1D1F3572003F94D7 /* Messages.swift in Sources */, 9E455BFA1BCE185B00070A4F /* EnumTests.swift in Sources */, 9E8E07241BD3F15800F98421 /* Food.swift in Sources */, + 520B867F1DCEB6A300885504 /* PropertyCompany.swift in Sources */, BD885BBE1D17358E00CA767A /* EncodeNestingTests.swift in Sources */, 9E455C0B1BCE1F0100070A4F /* RegularTests.swift in Sources */, 9E455C051BCE1D0700070A4F /* User.swift in Sources */, diff --git a/JSONCodable/JSONDecodable.swift b/JSONCodable/JSONDecodable.swift index 3cb8095..92073a9 100644 --- a/JSONCodable/JSONDecodable.swift +++ b/JSONCodable/JSONDecodable.swift @@ -69,14 +69,20 @@ public extension JSONDecodable { } public extension Array where Element: JSONDecodable { - init(JSONArray: [Any]) throws { + init(JSONArray: [Any], filtered: Bool = false) throws { self.init(try JSONArray.flatMap { guard let json = $0 as? [String : Any] else { throw JSONDecodableError.dictionaryTypeExpectedError(key: "n/a", elementType: type(of: $0)) } - return try Element(object: json) - }) + if filtered { + return try? Element(object: json) + } else { + return try Element(object: json) + } + }) } + + } // JSONDecoder - provides utility methods for decoding @@ -136,12 +142,12 @@ public class JSONDecoder { } return compatible } - + // JSONCompatible? public func decode(_ key: String) throws -> Compatible? { return (get(key) ?? object[key] as Any) as? Compatible } - + // JSONDecodable public func decode(_ key: String) throws -> Decodable { guard let value = get(key) else { @@ -193,7 +199,7 @@ public class JSONDecoder { } // [JSONCompatible] - public func decode(_ key: String) throws -> [Element] { + public func decode(_ key: String, filter: Bool = false) throws -> [Element] { guard let value = get(key) else { return [] } @@ -215,29 +221,41 @@ public class JSONDecoder { } // [JSONDecodable] - public func decode(_ key: String) throws -> [Element] { + public func decode(_ key: String, filter: Bool = false) throws -> [Element] { guard let value = get(key) else { return [] } guard let array = value as? [JSONObject] else { throw JSONDecodableError.arrayTypeExpectedError(key: key, elementType: type(of: value)) } - return try array.flatMap { try Element(object: $0)} + return try array.flatMap { + if filter { + return try? Element(object: $0) + } else { + return try Element(object: $0) + } + } } // [JSONDecodable]? - public func decode(_ key: String) throws -> [Element]? { + public func decode(_ key: String, filter: Bool = false) throws -> [Element]? { guard let value = get(key) else { return nil } guard let array = value as? [JSONObject] else { throw JSONDecodableError.arrayTypeExpectedError(key: key, elementType: type(of: value)) } - return try array.flatMap { try Element(object: $0)} + return try array.flatMap { + if filter { + return try? Element(object: $0) + } else { + return try Element(object: $0) + } + } } - + // [[JSONDecodable]] - public func decode(_ key: String) throws -> [[Element]] { + public func decode(_ key: String, filter: Bool = false) throws -> [[Element]] { guard let value = get(key) else { return [] } @@ -247,8 +265,13 @@ public class JSONDecoder { var res:[[Element]] = [] for x in array { - let nested = try x.flatMap { try Element(object: $0)} - res.append(nested) + if filter { + let nested = x.flatMap { try? Element(object: $0)} + res.append(nested) + } else { + let nested = try x.flatMap { try Element(object: $0)} + res.append(nested) + } } return res } diff --git a/JSONCodableTests/ArrayTests.swift b/JSONCodableTests/ArrayTests.swift new file mode 100644 index 0000000..ec1410d --- /dev/null +++ b/JSONCodableTests/ArrayTests.swift @@ -0,0 +1,85 @@ +// +// DictionaryTests.swift +// JSONCodable +// +// Created by FoxRichard on 11/5/16. +// +// + +import XCTest + +class ArrayTests: XCTestCase { + + let mixedArrayJSON = [ + [ + "class": "propertyType", + "rel": "propertyType", + "properties": + [ "name": "John", + "location": [ "coord": [ + "lat": 37.790770, + "long": -122.402015 + ]]]], + ["name": "CompanyInc", + "address": "1414 place st Los Angeles, CA"], + [ + "class": "propertyType", + "rel": "propertyType", + "properties": + [ "name": "Joe", + "location": [ "coord": [ + "lat": 38.790770, + "long": -121.402015 + ]]]], + ["name": "SoftwareInc", + "address": "1313 place st Oakland, CA"] + ] + + let companiesJSON: [[String: String]] = [ + ["name": "CompanyInc", + "address": "1414 place st Los Angeles, CA"], + ["name": "SoftwareInc", + "address": "1313 place st Oakland, CA"] + ] + + func testMixedItemsInArray() { + do { + let companies = try [Company](JSONArray: mixedArrayJSON, filtered: true) + guard let companyValues = try? companies.toJSON(), + let companiesEncoded:[[String: String]] = (companyValues as? [[String: String]]) else { + XCTFail() + return + } + XCTAssert(companiesEncoded.count == 2, "encoding invalid") + XCTAssert(companiesJSON.count == 2, "companies mapping invalid") + XCTAssert(companiesEncoded[0] == companiesJSON[0], "companies values incorrect") + XCTAssert(companiesEncoded[1] == companiesJSON[1], "companies values incorrect") + print(companies) + } catch { + print("\(error)") + XCTFail() + } + } + + func testMixedItemsInArrayNotFiltered() { + do { + let _ = try [Company](JSONArray: mixedArrayJSON, filtered: false) + XCTFail() + } catch { + print("mapping should fail if not filtered") + } + } + + func testCompanyProperties() { + let companyPropertiesJSON = ["companies_properties" : mixedArrayJSON] + do { + let companiesAndProperties = try PropertyCompany(object: companyPropertiesJSON) + print(companiesAndProperties) + } catch { + print(error) + XCTFail() + } + + + } +} diff --git a/JSONCodableTests/EnumTests.swift b/JSONCodableTests/EnumTests.swift index e805c24..5a1c9d7 100644 --- a/JSONCodableTests/EnumTests.swift +++ b/JSONCodableTests/EnumTests.swift @@ -19,7 +19,7 @@ class EnumTests: XCTestCase { func testDecodingEnum() { guard let fruit = try? Fruit(object: encodedValue) else { XCTFail() - return + return } XCTAssertEqual(fruit, decodedValue) diff --git a/JSONCodableTests/PropertyCompany.swift b/JSONCodableTests/PropertyCompany.swift new file mode 100644 index 0000000..3ca1c1d --- /dev/null +++ b/JSONCodableTests/PropertyCompany.swift @@ -0,0 +1,24 @@ +// +// PropertyCompany.swift +// JSONCodable +// +// Created by FoxRichard on 11/5/16. +// +// + +import JSONCodable + +struct PropertyCompany { + let properties: [PropertyItem] + let companies: [Company] +} + +extension PropertyCompany: JSONEncodable {} + +extension PropertyCompany: JSONDecodable { + init(object: JSONObject) throws { + let decoder = JSONDecoder(object: object) + properties = try decoder.decode("companies_properties", filter: true) + companies = try decoder.decode("companies_properties", filter: true) + } +} diff --git a/JSONCodableTests/PropertyItem.swift b/JSONCodableTests/PropertyItem.swift index 26b29e6..c942d10 100644 --- a/JSONCodableTests/PropertyItem.swift +++ b/JSONCodableTests/PropertyItem.swift @@ -18,16 +18,12 @@ struct PropertyItem { extension PropertyItem: JSONDecodable { init(object: JSONObject) throws { - do { - let decoder = JSONDecoder(object: object) - rel = try decoder.decode("rel") - type = try decoder.decode("class") - name = try decoder.decode("properties.name") - long = try decoder.decode("properties.location.coord.long") - lat = try decoder.decode("properties.location.coord.lat") - }catch{ - fatalError("\(error)") - } + let decoder = JSONDecoder(object: object) + rel = try decoder.decode("rel") + type = try decoder.decode("class") + name = try decoder.decode("properties.name") + long = try decoder.decode("properties.location.coord.long") + lat = try decoder.decode("properties.location.coord.lat") } }