├── .gitignore ├── .hound.yml ├── .swiftlint.yml ├── .travis.yml ├── CHANGELOG.md ├── Example.playground ├── Contents.swift ├── Resources │ └── example.json ├── contents.xcplayground └── playground.xcworkspace │ └── contents.xcworkspacedata ├── JSONUtilities.podspec ├── JSONUtilities.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── WorkspaceSettings.xcsettings └── xcshareddata │ └── xcschemes │ └── JSONUtilities.xcscheme ├── LICENSE ├── Metadata ├── Info.plist └── JSONUtilities.h ├── NonSwiftPackageManagerTests └── BundleFileLoadingTests.swift ├── Package.swift ├── README.md ├── Sources └── JSONUtilities │ ├── DecodingError.swift │ ├── Dictionary+JSONKey.swift │ ├── Dictionary+KeyPath.swift │ ├── InvalidItemBehaviour.swift │ ├── JSONFileLoading.swift │ ├── JSONObjectConvertible.swift │ ├── JSONPrimitiveConvertible.swift │ └── URL+JSONPrimitiveConvertible.swift ├── Tests ├── .swiftlint.yml └── JSONUtilitiesTests │ ├── Dictionary+KeyPathTests.swift │ ├── FileLoadingTests.swift │ ├── Helpers │ ├── Dictionary+Equatable.swift │ └── XCTestCase+Additions.swift │ ├── InlineDecodingTests.swift │ ├── InvalidItemBehaviourTests.swift │ ├── JSONDecodingTests.swift │ ├── JSONPrimitiveConvertibleTests.swift │ ├── Metadata │ └── Info.plist │ ├── Mocks │ ├── MockChild.swift │ ├── MockParent.swift │ ├── MockSimpleChild.swift │ └── MockSimpleParent.swift │ └── TestFiles │ ├── JSONFiles.swift │ ├── correct.json │ ├── correct_with_missing_nested_array.json │ ├── correct_with_missing_raw_array.json │ ├── correct_without_nested_object.json │ ├── empty.json │ ├── invalid.json │ └── root_array.json └── codecov.yml /.gitignore: -------------------------------------------------------------------------------- 1 | xcuserdata 2 | .DS_Store 3 | /.build 4 | /Packages 5 | /build 6 | -------------------------------------------------------------------------------- /.hound.yml: -------------------------------------------------------------------------------- 1 | swift: 2 | config_file: .swiftlint.yml -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | excluded: 2 | - Example.playground 3 | disabled_rules: 4 | - line_length 5 | - redundant_discardable_let 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: 2 | - osx 3 | language: generic 4 | osx_image: xcode10.3 5 | 6 | env: 7 | global: 8 | - PROJECT_NAME=JSONUtilities 9 | matrix: 10 | - PLATFORM=Mac 11 | - PLATFORM=iOS NAME='iPhone X' 12 | - PLATFORM=tvOS NAME='Apple TV 1080p' 13 | - PLATFORM=watchOS 14 | - PLATFORM=SPM 15 | 16 | script: 17 | - set -o pipefail; 18 | case $PLATFORM in 19 | Mac) 20 | xcodebuild -scheme $PROJECT_NAME -enableCodeCoverage YES test | xcpretty;; 21 | iOS) 22 | xcodebuild -scheme $PROJECT_NAME -destination "name=iPhone X" | xcpretty;; 23 | tvOS) 24 | xcodebuild -scheme $PROJECT_NAME -destination "name=Apple TV" | xcpretty;; 25 | watchOS) 26 | xcodebuild -scheme $PROJECT_NAME -destination "name=Apple Watch - 38mm" | xcpretty;; 27 | SPM) 28 | swift build && swift test;; 29 | esac 30 | 31 | after_success: 32 | - sleep 5 33 | - bash <(curl -s https://codecov.io/bash) 34 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## 6.0.1 4 | 5 | - Fixed throwing issue 6 | 7 | ## 6.0.0 8 | 9 | - Migrated to Swift 5 10 | 11 | 12 | ## 5.0.0 13 | 14 | - Migrated to Swift 4 15 | 16 | ## 4.0.0 17 | 18 | - Decoded typed dictionaries now filter out invalid objects by default instead of failing the whole response. This aligns with the defaults of how arrays are decoded. 19 | - Typed dictionaries and array functions now allow for specifiying the behaviour of when a child item encounters an error. See [InvalidItemBehaviour](Readme.md#InvalidItemBehaviour) for more details. The default is to remove these child items with `.remove` 20 | - DecodingError has been restructured, so that every error provides: 21 | - dictionary 22 | - keypath 23 | - expectedType 24 | - value 25 | - optional array if the error occured within an array 26 | - Each DecodingError type has also been moved into a simple reason enum. JSONPrimitiveConvertibleError has also been merged into DecodingError, which now as the following error reasons: 27 | - keyNotFound 28 | - incorrectRawRepresentableRawValue 29 | - incorrectType 30 | - conversionFailure 31 | - `[String: RawRepresentable]` can be now be decoded 32 | 33 | Thanks to [Yonas Kolb](https://github.com/yonaskolb) 34 | 35 | ## 3.2.0 36 | 37 | - This adds support for decoding typed dictionaries with a String key, specifically: 38 | - `[String: JSONRawType]` 39 | - `[String: JSONObjectConvertible]` 40 | - `[String: JSONPrimitiveConvertible]` 41 | 42 | Thanks to [Yonas Kolb](https://github.com/yonaskolb) 43 | 44 | ## 3.1.0 45 | 46 | - Added support for `URL` decoding out of the box thanks to [Sam Dods](https://github.com/samdods) 47 | 48 | ## 3.0.0 49 | 50 | ## New Features 51 | - Support for Swift 3 52 | - Keypath access in JSON dictionaries 53 | 54 | ## API updates 55 | - Renamed function for accessing values `jsonKey(_:)` to `json(atKeyPath:)` 56 | - Renamed `Transformable` protocol to `JSONPrimitiveConvertible` 57 | - Renamed `Decodable` protocol to `JSONObjectConvertible` 58 | - Renamed JSONDictionary loading functions: 59 | - `fromFile(_:)` to `from(filename:)` 60 | - `fromData(_:)` to `from(jsonData:)` 61 | 62 | ## 2.5.0 63 | 64 | - Added suppport for decoding arrays of `RawRepresentable` enums, i.e. `[RawRepresentable]` 65 | 66 | ## 2.4.0 67 | 68 | - Added suppport for `RawRepresentable` enums thanks to Yonas Kolb 69 | 70 | ## 2.3.0 71 | 72 | - Added support for decoding an array of `Tranformable` values 73 | 74 | ## 2.2.0 75 | 76 | - Added support for decoding a raw JSON dictionary and an array of raw JSON dictionary, i.e. `[String : AnyObject]` and `[[String : AnyObject]]` 77 | 78 | ## 2.1.0 79 | 80 | - Added `Tranformable` protocol to support decoding for custom types 81 | 82 | ## 2.0.1 83 | 84 | - Renamed `MandatoryLiteral` enum in `DecodingError` to `MandatoryKeyNotFound` for clarity 85 | 86 | ## 2.0.0 87 | 88 | - API now uses a functional approach. 89 | - `JSONDecoder` class and its associated methods have been replaced by an `extension` on `Dictionary`. The JSON key is now decoded by calling the `jsonKey(_:)` function. e.g.: 90 | 91 | ```swift 92 | let jsonDictionary = [ 93 | "key": "value", 94 | "key2": "value2" 95 | ] 96 | 97 | let myStringValue : String? = jsonDictionary.jsonKey("key") 98 | print(myStringValue) // "Optional("value")\n" 99 | ``` -------------------------------------------------------------------------------- /Example.playground/Contents.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import JSONUtilities 3 | 4 | // Simple example playground to see the loading and decoding of a JSON file 5 | 6 | struct Company { 7 | let name: String 8 | let employees: [Person] 9 | let referenceNumber: Int 10 | let owner: Person 11 | 12 | init(jsonDictionary: JSONDictionary) throws { 13 | name = try jsonDictionary.json(atKeyPath: "name") 14 | employees = try jsonDictionary.json(atKeyPath: "employees") 15 | referenceNumber = try jsonDictionary.json(atKeyPath: "attributes.reference-number") 16 | owner = try jsonDictionary.json(atKeyPath: "attributes.owner") 17 | } 18 | } 19 | 20 | // Nested struct 21 | struct Person: JSONObjectConvertible { 22 | 23 | let name: String 24 | let age: Int 25 | 26 | init(jsonDictionary: JSONDictionary) throws { 27 | name = try jsonDictionary.json(atKeyPath: "name") 28 | age = try jsonDictionary.json(atKeyPath: "age") 29 | } 30 | 31 | } 32 | 33 | do { 34 | let jsonDictionary = try JSONDictionary.from(filename: "example") 35 | let company = try Company(jsonDictionary: jsonDictionary) 36 | let rawEmployees: [JSONDictionary] = try jsonDictionary.json(atKeyPath: "employees") 37 | print(company.name) 38 | print(company.employees.first!.age) 39 | print(company.referenceNumber) 40 | print(company.owner) 41 | 42 | } catch { 43 | print(error) 44 | } 45 | 46 | class City { 47 | 48 | let name: String 49 | 50 | init(name: String) { 51 | self.name = name 52 | } 53 | 54 | init?(jsonDictionary: JSONDictionary) { 55 | do { 56 | name = try jsonDictionary.json(atKeyPath: "name") 57 | } catch { 58 | name = "" 59 | return nil 60 | } 61 | } 62 | 63 | convenience init(throwingJSONDictionary: JSONDictionary) throws { 64 | self.init( 65 | name : try throwingJSONDictionary.json(atKeyPath: "name") 66 | ) 67 | } 68 | } 69 | 70 | let validRawVehicleDictionary = ["name": "London"] 71 | let validCity = City(jsonDictionary: validRawVehicleDictionary) 72 | print(validCity?.name) 73 | 74 | let invalidRawVehicleDictionary = ["afe": "London"] 75 | let invalidCity = City(jsonDictionary: invalidRawVehicleDictionary) 76 | print(invalidCity?.name) 77 | 78 | do { 79 | let invalidCity = try City(throwingJSONDictionary: invalidRawVehicleDictionary) 80 | } catch { 81 | print(error) 82 | } 83 | 84 | enum State { 85 | case Normal 86 | case Selected 87 | } 88 | 89 | extension State : JSONPrimitiveConvertible { 90 | 91 | typealias JSONType = String 92 | 93 | static func from(jsonValue: String) -> State? { 94 | switch jsonValue.lowercased() { 95 | case "normal": 96 | return .Normal 97 | case "selected": 98 | return.Selected 99 | default: 100 | return nil 101 | } 102 | 103 | } 104 | 105 | } 106 | 107 | let jsonDictionary = ["state": "normal", "states": ["normal", "selected"]] as [String : Any] 108 | let stateNormal: State = try! jsonDictionary.json(atKeyPath: "state") 109 | let multipleStates: [State] = try! jsonDictionary.json(atKeyPath: "states") 110 | 111 | let urlDictionary = ["url": "www.google.com"] 112 | let url: URL = try! urlDictionary.json(atKeyPath: "url") // www.google.com 113 | let urlsDictionary = ["urls": ["www.google.com", "www.yahoo.com"]] 114 | let urls: [URL] = try! urlsDictionary.json(atKeyPath: "urls") // [www.google.com, www.yahoo.com] 115 | 116 | let rawDictionary: JSONDictionary = ["rootKey": 117 | [ 118 | "stringKey": "value", 119 | "numberKey": 1 120 | ] 121 | ] 122 | 123 | let decodedRawDictionary: JSONDictionary = try rawDictionary.json(atKeyPath: "rootKey") 124 | -------------------------------------------------------------------------------- /Example.playground/Resources/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "Working name LTD.", 3 | "attributes": { 4 | "reference-number": 1, 5 | "owner": { 6 | "name": "The Owner", 7 | "age": 29 8 | } 9 | }, 10 | "employees": [ 11 | { 12 | "name": "John Doe", 13 | "age": 24 14 | }, 15 | { 16 | "name": "Jane Doe", 17 | "age": 22 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /Example.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Example.playground/playground.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /JSONUtilities.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |spec| 2 | spec.name = 'JSONUtilities' 3 | spec.homepage = 'https://github.com/lucianomarisi/JSONUtilities' 4 | spec.version = '6.0.1' 5 | spec.license = { :type => 'MIT' } 6 | spec.authors = { 'Luciano Marisi' => 'luciano@techbrewers.com' } 7 | spec.summary = 'Easily load JSON objects and decode them into structs or classes' 8 | spec.source = { 9 | :git => "https://github.com/lucianomarisi/JSONUtilities.git", 10 | :tag => spec.version.to_s 11 | } 12 | spec.source_files = 'Sources/**/*.swift' 13 | spec.swift_versions = ['5.0'] 14 | spec.ios.deployment_target = '8.0' 15 | spec.tvos.deployment_target = '9.0' 16 | spec.osx.deployment_target = '10.10' 17 | spec.watchos.deployment_target = '2.0' 18 | end 19 | 20 | -------------------------------------------------------------------------------- /JSONUtilities.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 4F037C2A1D1B406D0075FC5E /* JSONObjectConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F037C281D1B406D0075FC5E /* JSONObjectConvertible.swift */; }; 11 | 4F037C2B1D1B406D0075FC5E /* JSONPrimitiveConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F037C291D1B406D0075FC5E /* JSONPrimitiveConvertible.swift */; }; 12 | 4F044FA71D7CB40E009FADD4 /* Dictionary+KeyPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F044FA61D7CB40E009FADD4 /* Dictionary+KeyPath.swift */; }; 13 | 4F05C4F41CE89191003D4718 /* InlineDecodingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F05C4F31CE89191003D4718 /* InlineDecodingTests.swift */; }; 14 | 4F05C4F61CE8C50F003D4718 /* MockSimpleChild.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F05C4F51CE8C50F003D4718 /* MockSimpleChild.swift */; }; 15 | 4F05C4F81CE8C518003D4718 /* MockSimpleParent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F05C4F71CE8C518003D4718 /* MockSimpleParent.swift */; }; 16 | 4F0842031C96143500C28467 /* JSONPrimitiveConvertibleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F0842021C96143500C28467 /* JSONPrimitiveConvertibleTests.swift */; }; 17 | 4F316CC11CE88599007F9241 /* MockParent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F316CC01CE88599007F9241 /* MockParent.swift */; }; 18 | 4F316CC61CE88898007F9241 /* XCTestCase+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F316CC51CE88898007F9241 /* XCTestCase+Additions.swift */; }; 19 | 4F4339691D3C159900C64537 /* Dictionary+Equatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F0F85FA1CDFEA6E00D2EEB6 /* Dictionary+Equatable.swift */; }; 20 | 4F43396B1D3C174000C64537 /* MockChild.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F316CC21CE885C8007F9241 /* MockChild.swift */; }; 21 | 4F47CDF81D1B3C4800F95472 /* Dictionary+JSONKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F47CDF21D1B3C4800F95472 /* Dictionary+JSONKey.swift */; }; 22 | 4F47CDF91D1B3C4800F95472 /* DecodingError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F47CDF31D1B3C4800F95472 /* DecodingError.swift */; }; 23 | 4F47CDFA1D1B3C4800F95472 /* JSONFileLoading.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F47CDF41D1B3C4800F95472 /* JSONFileLoading.swift */; }; 24 | 4F8ADC561C01F02B008FD528 /* correct_with_missing_raw_array.json in Resources */ = {isa = PBXBuildFile; fileRef = 4F8ADC551C01F02B008FD528 /* correct_with_missing_raw_array.json */; }; 25 | 4F8ADC581C01F100008FD528 /* correct_with_missing_nested_array.json in Resources */ = {isa = PBXBuildFile; fileRef = 4F8ADC571C01F100008FD528 /* correct_with_missing_nested_array.json */; }; 26 | 4FD43DD41D3C2D4E00075410 /* BundleFileLoadingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FD43DD21D3C2D4100075410 /* BundleFileLoadingTests.swift */; }; 27 | 4FD43DD81D3C317800075410 /* JSONFiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FE3C05B1C00F05700607CC4 /* JSONFiles.swift */; }; 28 | 4FE3C0291C00EDE900607CC4 /* JSONUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 4FE3C0281C00EDE900607CC4 /* JSONUtilities.h */; settings = {ATTRIBUTES = (Public, ); }; }; 29 | 4FE3C0301C00EDE900607CC4 /* JSONUtilities.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4FE3C0251C00EDE900607CC4 /* JSONUtilities.framework */; }; 30 | 4FE3C0351C00EDE900607CC4 /* FileLoadingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FE3C0341C00EDE900607CC4 /* FileLoadingTests.swift */; }; 31 | 4FE3C04F1C00EED400607CC4 /* correct.json in Resources */ = {isa = PBXBuildFile; fileRef = 4FE3C04A1C00EED400607CC4 /* correct.json */; }; 32 | 4FE3C0501C00EED400607CC4 /* correct_without_nested_object.json in Resources */ = {isa = PBXBuildFile; fileRef = 4FE3C04B1C00EED400607CC4 /* correct_without_nested_object.json */; }; 33 | 4FE3C0511C00EED400607CC4 /* invalid.json in Resources */ = {isa = PBXBuildFile; fileRef = 4FE3C04C1C00EED400607CC4 /* invalid.json */; }; 34 | 4FE3C0521C00EED400607CC4 /* root_array.json in Resources */ = {isa = PBXBuildFile; fileRef = 4FE3C04D1C00EED400607CC4 /* root_array.json */; }; 35 | 4FE3C0531C00EED400607CC4 /* empty.json in Resources */ = {isa = PBXBuildFile; fileRef = 4FE3C04E1C00EED400607CC4 /* empty.json */; }; 36 | 4FE3C0551C00EEE800607CC4 /* JSONDecodingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FE3C0541C00EEE800607CC4 /* JSONDecodingTests.swift */; }; 37 | 4FF31F3B1D7CBF8C00C9CCC8 /* Dictionary+KeyPathTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FF31F3A1D7CBF8C00C9CCC8 /* Dictionary+KeyPathTests.swift */; }; 38 | CC0188FF1EB2288A00D4EA87 /* InvalidItemBehaviour.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC0188FE1EB2288A00D4EA87 /* InvalidItemBehaviour.swift */; }; 39 | CCE82AD11EAA41900035C9F1 /* InvalidItemBehaviourTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCE82ACF1EAA41450035C9F1 /* InvalidItemBehaviourTests.swift */; }; 40 | EAD4CA401D894B0D001842D6 /* URL+JSONPrimitiveConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAD4CA3F1D894B0D001842D6 /* URL+JSONPrimitiveConvertible.swift */; }; 41 | /* End PBXBuildFile section */ 42 | 43 | /* Begin PBXContainerItemProxy section */ 44 | 4FE3C0311C00EDE900607CC4 /* PBXContainerItemProxy */ = { 45 | isa = PBXContainerItemProxy; 46 | containerPortal = 4FE3C01C1C00EDE900607CC4 /* Project object */; 47 | proxyType = 1; 48 | remoteGlobalIDString = 4FE3C0241C00EDE900607CC4; 49 | remoteInfo = JSONUtilities; 50 | }; 51 | /* End PBXContainerItemProxy section */ 52 | 53 | /* Begin PBXFileReference section */ 54 | 4F037C281D1B406D0075FC5E /* JSONObjectConvertible.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSONObjectConvertible.swift; sourceTree = ""; }; 55 | 4F037C291D1B406D0075FC5E /* JSONPrimitiveConvertible.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSONPrimitiveConvertible.swift; sourceTree = ""; }; 56 | 4F044FA61D7CB40E009FADD4 /* Dictionary+KeyPath.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Dictionary+KeyPath.swift"; sourceTree = ""; }; 57 | 4F05C4F31CE89191003D4718 /* InlineDecodingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InlineDecodingTests.swift; sourceTree = ""; }; 58 | 4F05C4F51CE8C50F003D4718 /* MockSimpleChild.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockSimpleChild.swift; sourceTree = ""; }; 59 | 4F05C4F71CE8C518003D4718 /* MockSimpleParent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockSimpleParent.swift; sourceTree = ""; }; 60 | 4F0842021C96143500C28467 /* JSONPrimitiveConvertibleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSONPrimitiveConvertibleTests.swift; sourceTree = ""; }; 61 | 4F0F85FA1CDFEA6E00D2EEB6 /* Dictionary+Equatable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Dictionary+Equatable.swift"; sourceTree = ""; }; 62 | 4F316CC01CE88599007F9241 /* MockParent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockParent.swift; sourceTree = ""; }; 63 | 4F316CC21CE885C8007F9241 /* MockChild.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockChild.swift; sourceTree = ""; }; 64 | 4F316CC51CE88898007F9241 /* XCTestCase+Additions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "XCTestCase+Additions.swift"; sourceTree = ""; }; 65 | 4F47CDF21D1B3C4800F95472 /* Dictionary+JSONKey.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Dictionary+JSONKey.swift"; sourceTree = ""; }; 66 | 4F47CDF31D1B3C4800F95472 /* DecodingError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DecodingError.swift; sourceTree = ""; }; 67 | 4F47CDF41D1B3C4800F95472 /* JSONFileLoading.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSONFileLoading.swift; sourceTree = ""; }; 68 | 4F7429F91D7CA7940027EAAD /* Example.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = Example.playground; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 69 | 4F8ADC551C01F02B008FD528 /* correct_with_missing_raw_array.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = correct_with_missing_raw_array.json; sourceTree = ""; }; 70 | 4F8ADC571C01F100008FD528 /* correct_with_missing_nested_array.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = correct_with_missing_nested_array.json; sourceTree = ""; }; 71 | 4FD43DD21D3C2D4100075410 /* BundleFileLoadingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BundleFileLoadingTests.swift; sourceTree = ""; }; 72 | 4FE3C0251C00EDE900607CC4 /* JSONUtilities.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = JSONUtilities.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 73 | 4FE3C0281C00EDE900607CC4 /* JSONUtilities.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JSONUtilities.h; sourceTree = ""; }; 74 | 4FE3C02A1C00EDE900607CC4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 75 | 4FE3C02F1C00EDE900607CC4 /* JSONUtilitiesTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = JSONUtilitiesTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 76 | 4FE3C0341C00EDE900607CC4 /* FileLoadingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileLoadingTests.swift; sourceTree = ""; }; 77 | 4FE3C0361C00EDE900607CC4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 78 | 4FE3C04A1C00EED400607CC4 /* correct.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = correct.json; sourceTree = ""; }; 79 | 4FE3C04B1C00EED400607CC4 /* correct_without_nested_object.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = correct_without_nested_object.json; sourceTree = ""; }; 80 | 4FE3C04C1C00EED400607CC4 /* invalid.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = invalid.json; sourceTree = ""; }; 81 | 4FE3C04D1C00EED400607CC4 /* root_array.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = root_array.json; sourceTree = ""; }; 82 | 4FE3C04E1C00EED400607CC4 /* empty.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = empty.json; sourceTree = ""; }; 83 | 4FE3C0541C00EEE800607CC4 /* JSONDecodingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSONDecodingTests.swift; sourceTree = ""; }; 84 | 4FE3C05B1C00F05700607CC4 /* JSONFiles.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSONFiles.swift; sourceTree = ""; }; 85 | 4FF31F3A1D7CBF8C00C9CCC8 /* Dictionary+KeyPathTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Dictionary+KeyPathTests.swift"; sourceTree = ""; }; 86 | CC0188FE1EB2288A00D4EA87 /* InvalidItemBehaviour.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InvalidItemBehaviour.swift; sourceTree = ""; }; 87 | CCE82ACF1EAA41450035C9F1 /* InvalidItemBehaviourTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InvalidItemBehaviourTests.swift; sourceTree = ""; }; 88 | EAD4CA3F1D894B0D001842D6 /* URL+JSONPrimitiveConvertible.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "URL+JSONPrimitiveConvertible.swift"; sourceTree = ""; }; 89 | /* End PBXFileReference section */ 90 | 91 | /* Begin PBXFrameworksBuildPhase section */ 92 | 4FE3C0211C00EDE900607CC4 /* Frameworks */ = { 93 | isa = PBXFrameworksBuildPhase; 94 | buildActionMask = 2147483647; 95 | files = ( 96 | ); 97 | runOnlyForDeploymentPostprocessing = 0; 98 | }; 99 | 4FE3C02C1C00EDE900607CC4 /* Frameworks */ = { 100 | isa = PBXFrameworksBuildPhase; 101 | buildActionMask = 2147483647; 102 | files = ( 103 | 4FE3C0301C00EDE900607CC4 /* JSONUtilities.framework in Frameworks */, 104 | ); 105 | runOnlyForDeploymentPostprocessing = 0; 106 | }; 107 | /* End PBXFrameworksBuildPhase section */ 108 | 109 | /* Begin PBXGroup section */ 110 | 4F316CC41CE8869E007F9241 /* Helpers */ = { 111 | isa = PBXGroup; 112 | children = ( 113 | 4F0F85FA1CDFEA6E00D2EEB6 /* Dictionary+Equatable.swift */, 114 | 4F316CC51CE88898007F9241 /* XCTestCase+Additions.swift */, 115 | ); 116 | path = Helpers; 117 | sourceTree = ""; 118 | }; 119 | 4F316CC71CE889EE007F9241 /* Metadata */ = { 120 | isa = PBXGroup; 121 | children = ( 122 | 4FE3C02A1C00EDE900607CC4 /* Info.plist */, 123 | 4FE3C0281C00EDE900607CC4 /* JSONUtilities.h */, 124 | ); 125 | path = Metadata; 126 | sourceTree = ""; 127 | }; 128 | 4F316CC81CE88BA6007F9241 /* Metadata */ = { 129 | isa = PBXGroup; 130 | children = ( 131 | 4FE3C0361C00EDE900607CC4 /* Info.plist */, 132 | ); 133 | path = Metadata; 134 | sourceTree = ""; 135 | }; 136 | 4F47CDEF1D1B3C4800F95472 /* Sources */ = { 137 | isa = PBXGroup; 138 | children = ( 139 | 4F7429FC1D7CAE9E0027EAAD /* JSONUtilities */, 140 | ); 141 | path = Sources; 142 | sourceTree = ""; 143 | }; 144 | 4F7429FB1D7CAE900027EAAD /* JSONUtilitiesTests */ = { 145 | isa = PBXGroup; 146 | children = ( 147 | 4F316CC41CE8869E007F9241 /* Helpers */, 148 | 4F316CC81CE88BA6007F9241 /* Metadata */, 149 | 4FE3C0561C00EF0D00607CC4 /* Mocks */, 150 | 4FE3C0431C00EEB200607CC4 /* TestFiles */, 151 | 4FE3C0341C00EDE900607CC4 /* FileLoadingTests.swift */, 152 | 4F05C4F31CE89191003D4718 /* InlineDecodingTests.swift */, 153 | 4FE3C0541C00EEE800607CC4 /* JSONDecodingTests.swift */, 154 | 4F0842021C96143500C28467 /* JSONPrimitiveConvertibleTests.swift */, 155 | 4FF31F3A1D7CBF8C00C9CCC8 /* Dictionary+KeyPathTests.swift */, 156 | CCE82ACF1EAA41450035C9F1 /* InvalidItemBehaviourTests.swift */, 157 | ); 158 | path = JSONUtilitiesTests; 159 | sourceTree = ""; 160 | }; 161 | 4F7429FC1D7CAE9E0027EAAD /* JSONUtilities */ = { 162 | isa = PBXGroup; 163 | children = ( 164 | 4F47CDF21D1B3C4800F95472 /* Dictionary+JSONKey.swift */, 165 | 4F044FA61D7CB40E009FADD4 /* Dictionary+KeyPath.swift */, 166 | 4F47CDF31D1B3C4800F95472 /* DecodingError.swift */, 167 | 4F47CDF41D1B3C4800F95472 /* JSONFileLoading.swift */, 168 | 4F037C281D1B406D0075FC5E /* JSONObjectConvertible.swift */, 169 | 4F037C291D1B406D0075FC5E /* JSONPrimitiveConvertible.swift */, 170 | EAD4CA3F1D894B0D001842D6 /* URL+JSONPrimitiveConvertible.swift */, 171 | CC0188FE1EB2288A00D4EA87 /* InvalidItemBehaviour.swift */, 172 | ); 173 | path = JSONUtilities; 174 | sourceTree = ""; 175 | }; 176 | 4FD43DD51D3C2D7900075410 /* NonSwiftPackageManagerTests */ = { 177 | isa = PBXGroup; 178 | children = ( 179 | 4FD43DD21D3C2D4100075410 /* BundleFileLoadingTests.swift */, 180 | ); 181 | path = NonSwiftPackageManagerTests; 182 | sourceTree = ""; 183 | }; 184 | 4FE3C01B1C00EDE900607CC4 = { 185 | isa = PBXGroup; 186 | children = ( 187 | 4F7429F91D7CA7940027EAAD /* Example.playground */, 188 | 4F316CC71CE889EE007F9241 /* Metadata */, 189 | 4FD43DD51D3C2D7900075410 /* NonSwiftPackageManagerTests */, 190 | 4FE3C0261C00EDE900607CC4 /* Products */, 191 | 4F47CDEF1D1B3C4800F95472 /* Sources */, 192 | 4FE3C0331C00EDE900607CC4 /* Tests */, 193 | ); 194 | indentWidth = 2; 195 | sourceTree = ""; 196 | tabWidth = 2; 197 | usesTabs = 0; 198 | }; 199 | 4FE3C0261C00EDE900607CC4 /* Products */ = { 200 | isa = PBXGroup; 201 | children = ( 202 | 4FE3C0251C00EDE900607CC4 /* JSONUtilities.framework */, 203 | 4FE3C02F1C00EDE900607CC4 /* JSONUtilitiesTests.xctest */, 204 | ); 205 | name = Products; 206 | sourceTree = ""; 207 | }; 208 | 4FE3C0331C00EDE900607CC4 /* Tests */ = { 209 | isa = PBXGroup; 210 | children = ( 211 | 4F7429FB1D7CAE900027EAAD /* JSONUtilitiesTests */, 212 | ); 213 | path = Tests; 214 | sourceTree = ""; 215 | }; 216 | 4FE3C0431C00EEB200607CC4 /* TestFiles */ = { 217 | isa = PBXGroup; 218 | children = ( 219 | 4FE3C05B1C00F05700607CC4 /* JSONFiles.swift */, 220 | 4FE3C04A1C00EED400607CC4 /* correct.json */, 221 | 4F8ADC571C01F100008FD528 /* correct_with_missing_nested_array.json */, 222 | 4F8ADC551C01F02B008FD528 /* correct_with_missing_raw_array.json */, 223 | 4FE3C04B1C00EED400607CC4 /* correct_without_nested_object.json */, 224 | 4FE3C04E1C00EED400607CC4 /* empty.json */, 225 | 4FE3C04C1C00EED400607CC4 /* invalid.json */, 226 | 4FE3C04D1C00EED400607CC4 /* root_array.json */, 227 | ); 228 | path = TestFiles; 229 | sourceTree = ""; 230 | }; 231 | 4FE3C0561C00EF0D00607CC4 /* Mocks */ = { 232 | isa = PBXGroup; 233 | children = ( 234 | 4F316CC21CE885C8007F9241 /* MockChild.swift */, 235 | 4F316CC01CE88599007F9241 /* MockParent.swift */, 236 | 4F05C4F51CE8C50F003D4718 /* MockSimpleChild.swift */, 237 | 4F05C4F71CE8C518003D4718 /* MockSimpleParent.swift */, 238 | ); 239 | path = Mocks; 240 | sourceTree = ""; 241 | }; 242 | /* End PBXGroup section */ 243 | 244 | /* Begin PBXHeadersBuildPhase section */ 245 | 4FE3C0221C00EDE900607CC4 /* Headers */ = { 246 | isa = PBXHeadersBuildPhase; 247 | buildActionMask = 2147483647; 248 | files = ( 249 | 4FE3C0291C00EDE900607CC4 /* JSONUtilities.h in Headers */, 250 | ); 251 | runOnlyForDeploymentPostprocessing = 0; 252 | }; 253 | /* End PBXHeadersBuildPhase section */ 254 | 255 | /* Begin PBXNativeTarget section */ 256 | 4FE3C0241C00EDE900607CC4 /* JSONUtilities */ = { 257 | isa = PBXNativeTarget; 258 | buildConfigurationList = 4FE3C0391C00EDE900607CC4 /* Build configuration list for PBXNativeTarget "JSONUtilities" */; 259 | buildPhases = ( 260 | 4F0109921DFEB65800431494 /* SwiftLint */, 261 | 4FE3C0201C00EDE900607CC4 /* Sources */, 262 | 4FE3C0211C00EDE900607CC4 /* Frameworks */, 263 | 4FE3C0221C00EDE900607CC4 /* Headers */, 264 | 4FE3C0231C00EDE900607CC4 /* Resources */, 265 | ); 266 | buildRules = ( 267 | ); 268 | dependencies = ( 269 | ); 270 | name = JSONUtilities; 271 | productName = JSONUtilities; 272 | productReference = 4FE3C0251C00EDE900607CC4 /* JSONUtilities.framework */; 273 | productType = "com.apple.product-type.framework"; 274 | }; 275 | 4FE3C02E1C00EDE900607CC4 /* JSONUtilitiesTests */ = { 276 | isa = PBXNativeTarget; 277 | buildConfigurationList = 4FE3C03C1C00EDE900607CC4 /* Build configuration list for PBXNativeTarget "JSONUtilitiesTests" */; 278 | buildPhases = ( 279 | 4FE3C02B1C00EDE900607CC4 /* Sources */, 280 | 4FE3C02C1C00EDE900607CC4 /* Frameworks */, 281 | 4FE3C02D1C00EDE900607CC4 /* Resources */, 282 | ); 283 | buildRules = ( 284 | ); 285 | dependencies = ( 286 | 4FE3C0321C00EDE900607CC4 /* PBXTargetDependency */, 287 | ); 288 | name = JSONUtilitiesTests; 289 | productName = JSONUtilitiesTests; 290 | productReference = 4FE3C02F1C00EDE900607CC4 /* JSONUtilitiesTests.xctest */; 291 | productType = "com.apple.product-type.bundle.unit-test"; 292 | }; 293 | /* End PBXNativeTarget section */ 294 | 295 | /* Begin PBXProject section */ 296 | 4FE3C01C1C00EDE900607CC4 /* Project object */ = { 297 | isa = PBXProject; 298 | attributes = { 299 | LastSwiftUpdateCheck = 0710; 300 | LastUpgradeCheck = 1020; 301 | ORGANIZATIONNAME = "Luciano Marisi"; 302 | TargetAttributes = { 303 | 4FE3C0241C00EDE900607CC4 = { 304 | CreatedOnToolsVersion = 7.1; 305 | LastSwiftMigration = 1020; 306 | ProvisioningStyle = Manual; 307 | }; 308 | 4FE3C02E1C00EDE900607CC4 = { 309 | CreatedOnToolsVersion = 7.1; 310 | LastSwiftMigration = 1020; 311 | }; 312 | }; 313 | }; 314 | buildConfigurationList = 4FE3C01F1C00EDE900607CC4 /* Build configuration list for PBXProject "JSONUtilities" */; 315 | compatibilityVersion = "Xcode 3.2"; 316 | developmentRegion = en; 317 | hasScannedForEncodings = 0; 318 | knownRegions = ( 319 | en, 320 | Base, 321 | ); 322 | mainGroup = 4FE3C01B1C00EDE900607CC4; 323 | productRefGroup = 4FE3C0261C00EDE900607CC4 /* Products */; 324 | projectDirPath = ""; 325 | projectRoot = ""; 326 | targets = ( 327 | 4FE3C0241C00EDE900607CC4 /* JSONUtilities */, 328 | 4FE3C02E1C00EDE900607CC4 /* JSONUtilitiesTests */, 329 | ); 330 | }; 331 | /* End PBXProject section */ 332 | 333 | /* Begin PBXResourcesBuildPhase section */ 334 | 4FE3C0231C00EDE900607CC4 /* Resources */ = { 335 | isa = PBXResourcesBuildPhase; 336 | buildActionMask = 2147483647; 337 | files = ( 338 | ); 339 | runOnlyForDeploymentPostprocessing = 0; 340 | }; 341 | 4FE3C02D1C00EDE900607CC4 /* Resources */ = { 342 | isa = PBXResourcesBuildPhase; 343 | buildActionMask = 2147483647; 344 | files = ( 345 | 4F8ADC581C01F100008FD528 /* correct_with_missing_nested_array.json in Resources */, 346 | 4FE3C04F1C00EED400607CC4 /* correct.json in Resources */, 347 | 4FE3C0511C00EED400607CC4 /* invalid.json in Resources */, 348 | 4F8ADC561C01F02B008FD528 /* correct_with_missing_raw_array.json in Resources */, 349 | 4FE3C0501C00EED400607CC4 /* correct_without_nested_object.json in Resources */, 350 | 4FE3C0521C00EED400607CC4 /* root_array.json in Resources */, 351 | 4FE3C0531C00EED400607CC4 /* empty.json in Resources */, 352 | ); 353 | runOnlyForDeploymentPostprocessing = 0; 354 | }; 355 | /* End PBXResourcesBuildPhase section */ 356 | 357 | /* Begin PBXShellScriptBuildPhase section */ 358 | 4F0109921DFEB65800431494 /* SwiftLint */ = { 359 | isa = PBXShellScriptBuildPhase; 360 | buildActionMask = 2147483647; 361 | files = ( 362 | ); 363 | inputPaths = ( 364 | ); 365 | name = SwiftLint; 366 | outputPaths = ( 367 | ); 368 | runOnlyForDeploymentPostprocessing = 0; 369 | shellPath = /bin/sh; 370 | shellScript = "if which swiftlint >/dev/null; then\n swiftlint\n swiftlint autocorrect\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi"; 371 | }; 372 | /* End PBXShellScriptBuildPhase section */ 373 | 374 | /* Begin PBXSourcesBuildPhase section */ 375 | 4FE3C0201C00EDE900607CC4 /* Sources */ = { 376 | isa = PBXSourcesBuildPhase; 377 | buildActionMask = 2147483647; 378 | files = ( 379 | 4F47CDFA1D1B3C4800F95472 /* JSONFileLoading.swift in Sources */, 380 | 4F47CDF81D1B3C4800F95472 /* Dictionary+JSONKey.swift in Sources */, 381 | 4F044FA71D7CB40E009FADD4 /* Dictionary+KeyPath.swift in Sources */, 382 | 4F47CDF91D1B3C4800F95472 /* DecodingError.swift in Sources */, 383 | 4F037C2B1D1B406D0075FC5E /* JSONPrimitiveConvertible.swift in Sources */, 384 | CC0188FF1EB2288A00D4EA87 /* InvalidItemBehaviour.swift in Sources */, 385 | 4F037C2A1D1B406D0075FC5E /* JSONObjectConvertible.swift in Sources */, 386 | EAD4CA401D894B0D001842D6 /* URL+JSONPrimitiveConvertible.swift in Sources */, 387 | ); 388 | runOnlyForDeploymentPostprocessing = 0; 389 | }; 390 | 4FE3C02B1C00EDE900607CC4 /* Sources */ = { 391 | isa = PBXSourcesBuildPhase; 392 | buildActionMask = 2147483647; 393 | files = ( 394 | 4FE3C0551C00EEE800607CC4 /* JSONDecodingTests.swift in Sources */, 395 | 4F43396B1D3C174000C64537 /* MockChild.swift in Sources */, 396 | 4FD43DD41D3C2D4E00075410 /* BundleFileLoadingTests.swift in Sources */, 397 | 4F316CC11CE88599007F9241 /* MockParent.swift in Sources */, 398 | 4F05C4F81CE8C518003D4718 /* MockSimpleParent.swift in Sources */, 399 | 4FE3C0351C00EDE900607CC4 /* FileLoadingTests.swift in Sources */, 400 | 4F05C4F41CE89191003D4718 /* InlineDecodingTests.swift in Sources */, 401 | 4F05C4F61CE8C50F003D4718 /* MockSimpleChild.swift in Sources */, 402 | CCE82AD11EAA41900035C9F1 /* InvalidItemBehaviourTests.swift in Sources */, 403 | 4F0842031C96143500C28467 /* JSONPrimitiveConvertibleTests.swift in Sources */, 404 | 4F316CC61CE88898007F9241 /* XCTestCase+Additions.swift in Sources */, 405 | 4FF31F3B1D7CBF8C00C9CCC8 /* Dictionary+KeyPathTests.swift in Sources */, 406 | 4F4339691D3C159900C64537 /* Dictionary+Equatable.swift in Sources */, 407 | 4FD43DD81D3C317800075410 /* JSONFiles.swift in Sources */, 408 | ); 409 | runOnlyForDeploymentPostprocessing = 0; 410 | }; 411 | /* End PBXSourcesBuildPhase section */ 412 | 413 | /* Begin PBXTargetDependency section */ 414 | 4FE3C0321C00EDE900607CC4 /* PBXTargetDependency */ = { 415 | isa = PBXTargetDependency; 416 | target = 4FE3C0241C00EDE900607CC4 /* JSONUtilities */; 417 | targetProxy = 4FE3C0311C00EDE900607CC4 /* PBXContainerItemProxy */; 418 | }; 419 | /* End PBXTargetDependency section */ 420 | 421 | /* Begin XCBuildConfiguration section */ 422 | 4FE3C0371C00EDE900607CC4 /* Debug */ = { 423 | isa = XCBuildConfiguration; 424 | buildSettings = { 425 | ALWAYS_SEARCH_USER_PATHS = NO; 426 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 427 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 428 | CLANG_CXX_LIBRARY = "libc++"; 429 | CLANG_ENABLE_MODULES = YES; 430 | CLANG_ENABLE_OBJC_ARC = YES; 431 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 432 | CLANG_WARN_BOOL_CONVERSION = YES; 433 | CLANG_WARN_COMMA = YES; 434 | CLANG_WARN_CONSTANT_CONVERSION = YES; 435 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 436 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 437 | CLANG_WARN_EMPTY_BODY = YES; 438 | CLANG_WARN_ENUM_CONVERSION = YES; 439 | CLANG_WARN_INFINITE_RECURSION = YES; 440 | CLANG_WARN_INT_CONVERSION = YES; 441 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 442 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 443 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 444 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 445 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 446 | CLANG_WARN_STRICT_PROTOTYPES = YES; 447 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 448 | CLANG_WARN_UNREACHABLE_CODE = YES; 449 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 450 | COPY_PHASE_STRIP = NO; 451 | CURRENT_PROJECT_VERSION = 1; 452 | DEBUG_INFORMATION_FORMAT = dwarf; 453 | ENABLE_STRICT_OBJC_MSGSEND = YES; 454 | ENABLE_TESTABILITY = YES; 455 | GCC_C_LANGUAGE_STANDARD = gnu99; 456 | GCC_DYNAMIC_NO_PIC = NO; 457 | GCC_NO_COMMON_BLOCKS = YES; 458 | GCC_OPTIMIZATION_LEVEL = 0; 459 | GCC_PREPROCESSOR_DEFINITIONS = ( 460 | "DEBUG=1", 461 | "$(inherited)", 462 | ); 463 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 464 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 465 | GCC_WARN_UNDECLARED_SELECTOR = YES; 466 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 467 | GCC_WARN_UNUSED_FUNCTION = YES; 468 | GCC_WARN_UNUSED_VARIABLE = YES; 469 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 470 | MACOSX_DEPLOYMENT_TARGET = 10.10; 471 | MTL_ENABLE_DEBUG_INFO = YES; 472 | ONLY_ACTIVE_ARCH = YES; 473 | SUPPORTED_PLATFORMS = "iphonesimulator iphoneos macosx appletvos watchos appletvsimulator watchsimulator"; 474 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 475 | TARGETED_DEVICE_FAMILY = "1,2,3,4"; 476 | TVOS_DEPLOYMENT_TARGET = 9.0; 477 | VERSIONING_SYSTEM = "apple-generic"; 478 | VERSION_INFO_PREFIX = ""; 479 | WATCHOS_DEPLOYMENT_TARGET = 2.0; 480 | }; 481 | name = Debug; 482 | }; 483 | 4FE3C0381C00EDE900607CC4 /* Release */ = { 484 | isa = XCBuildConfiguration; 485 | buildSettings = { 486 | ALWAYS_SEARCH_USER_PATHS = NO; 487 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 488 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 489 | CLANG_CXX_LIBRARY = "libc++"; 490 | CLANG_ENABLE_MODULES = YES; 491 | CLANG_ENABLE_OBJC_ARC = YES; 492 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 493 | CLANG_WARN_BOOL_CONVERSION = YES; 494 | CLANG_WARN_COMMA = YES; 495 | CLANG_WARN_CONSTANT_CONVERSION = YES; 496 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 497 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 498 | CLANG_WARN_EMPTY_BODY = YES; 499 | CLANG_WARN_ENUM_CONVERSION = YES; 500 | CLANG_WARN_INFINITE_RECURSION = YES; 501 | CLANG_WARN_INT_CONVERSION = YES; 502 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 503 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 504 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 505 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 506 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 507 | CLANG_WARN_STRICT_PROTOTYPES = YES; 508 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 509 | CLANG_WARN_UNREACHABLE_CODE = YES; 510 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 511 | COPY_PHASE_STRIP = NO; 512 | CURRENT_PROJECT_VERSION = 1; 513 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 514 | ENABLE_NS_ASSERTIONS = NO; 515 | ENABLE_STRICT_OBJC_MSGSEND = YES; 516 | GCC_C_LANGUAGE_STANDARD = gnu99; 517 | GCC_NO_COMMON_BLOCKS = YES; 518 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 519 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 520 | GCC_WARN_UNDECLARED_SELECTOR = YES; 521 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 522 | GCC_WARN_UNUSED_FUNCTION = YES; 523 | GCC_WARN_UNUSED_VARIABLE = YES; 524 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 525 | MACOSX_DEPLOYMENT_TARGET = 10.10; 526 | MTL_ENABLE_DEBUG_INFO = NO; 527 | SUPPORTED_PLATFORMS = "iphonesimulator iphoneos macosx appletvos watchos appletvsimulator watchsimulator"; 528 | TARGETED_DEVICE_FAMILY = "1,2,3,4"; 529 | TVOS_DEPLOYMENT_TARGET = 9.0; 530 | VALIDATE_PRODUCT = YES; 531 | VERSIONING_SYSTEM = "apple-generic"; 532 | VERSION_INFO_PREFIX = ""; 533 | WATCHOS_DEPLOYMENT_TARGET = 2.0; 534 | }; 535 | name = Release; 536 | }; 537 | 4FE3C03A1C00EDE900607CC4 /* Debug */ = { 538 | isa = XCBuildConfiguration; 539 | buildSettings = { 540 | CLANG_ENABLE_MODULES = YES; 541 | DEFINES_MODULE = YES; 542 | DYLIB_COMPATIBILITY_VERSION = 1; 543 | DYLIB_CURRENT_VERSION = 1; 544 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 545 | INFOPLIST_FILE = Metadata/Info.plist; 546 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 547 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 548 | PRODUCT_BUNDLE_IDENTIFIER = "com.TechBrewers-LTD.JSONUtilities"; 549 | PRODUCT_NAME = "$(TARGET_NAME)"; 550 | SKIP_INSTALL = YES; 551 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 552 | SWIFT_VERSION = 5.0; 553 | }; 554 | name = Debug; 555 | }; 556 | 4FE3C03B1C00EDE900607CC4 /* Release */ = { 557 | isa = XCBuildConfiguration; 558 | buildSettings = { 559 | CLANG_ENABLE_MODULES = YES; 560 | DEFINES_MODULE = YES; 561 | DYLIB_COMPATIBILITY_VERSION = 1; 562 | DYLIB_CURRENT_VERSION = 1; 563 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 564 | INFOPLIST_FILE = Metadata/Info.plist; 565 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 566 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 567 | PRODUCT_BUNDLE_IDENTIFIER = "com.TechBrewers-LTD.JSONUtilities"; 568 | PRODUCT_NAME = "$(TARGET_NAME)"; 569 | SKIP_INSTALL = YES; 570 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 571 | SWIFT_VERSION = 5.0; 572 | }; 573 | name = Release; 574 | }; 575 | 4FE3C03D1C00EDE900607CC4 /* Debug */ = { 576 | isa = XCBuildConfiguration; 577 | buildSettings = { 578 | INFOPLIST_FILE = Tests/JSONUtilitiesTests/Metadata/Info.plist; 579 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks @executable_path/../Frameworks @loader_path/../Frameworks"; 580 | PRODUCT_BUNDLE_IDENTIFIER = "com.TechBrewers-LTD.JSONUtilitiesTests"; 581 | PRODUCT_NAME = "$(TARGET_NAME)"; 582 | SUPPORTED_PLATFORMS = "iphonesimulator iphoneos macosx appletvos appletvsimulator"; 583 | SWIFT_VERSION = 5.0; 584 | }; 585 | name = Debug; 586 | }; 587 | 4FE3C03E1C00EDE900607CC4 /* Release */ = { 588 | isa = XCBuildConfiguration; 589 | buildSettings = { 590 | INFOPLIST_FILE = Tests/JSONUtilitiesTests/Metadata/Info.plist; 591 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks @executable_path/../Frameworks @loader_path/../Frameworks"; 592 | PRODUCT_BUNDLE_IDENTIFIER = "com.TechBrewers-LTD.JSONUtilitiesTests"; 593 | PRODUCT_NAME = "$(TARGET_NAME)"; 594 | SUPPORTED_PLATFORMS = "iphonesimulator iphoneos macosx appletvos appletvsimulator"; 595 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 596 | SWIFT_VERSION = 5.0; 597 | }; 598 | name = Release; 599 | }; 600 | /* End XCBuildConfiguration section */ 601 | 602 | /* Begin XCConfigurationList section */ 603 | 4FE3C01F1C00EDE900607CC4 /* Build configuration list for PBXProject "JSONUtilities" */ = { 604 | isa = XCConfigurationList; 605 | buildConfigurations = ( 606 | 4FE3C0371C00EDE900607CC4 /* Debug */, 607 | 4FE3C0381C00EDE900607CC4 /* Release */, 608 | ); 609 | defaultConfigurationIsVisible = 0; 610 | defaultConfigurationName = Release; 611 | }; 612 | 4FE3C0391C00EDE900607CC4 /* Build configuration list for PBXNativeTarget "JSONUtilities" */ = { 613 | isa = XCConfigurationList; 614 | buildConfigurations = ( 615 | 4FE3C03A1C00EDE900607CC4 /* Debug */, 616 | 4FE3C03B1C00EDE900607CC4 /* Release */, 617 | ); 618 | defaultConfigurationIsVisible = 0; 619 | defaultConfigurationName = Release; 620 | }; 621 | 4FE3C03C1C00EDE900607CC4 /* Build configuration list for PBXNativeTarget "JSONUtilitiesTests" */ = { 622 | isa = XCConfigurationList; 623 | buildConfigurations = ( 624 | 4FE3C03D1C00EDE900607CC4 /* Debug */, 625 | 4FE3C03E1C00EDE900607CC4 /* Release */, 626 | ); 627 | defaultConfigurationIsVisible = 0; 628 | defaultConfigurationName = Release; 629 | }; 630 | /* End XCConfigurationList section */ 631 | }; 632 | rootObject = 4FE3C01C1C00EDE900607CC4 /* Project object */; 633 | } 634 | -------------------------------------------------------------------------------- /JSONUtilities.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /JSONUtilities.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /JSONUtilities.xcodeproj/xcshareddata/xcschemes/JSONUtilities.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 34 | 40 | 41 | 42 | 43 | 44 | 50 | 51 | 52 | 53 | 54 | 55 | 65 | 66 | 72 | 73 | 74 | 75 | 76 | 77 | 83 | 84 | 90 | 91 | 92 | 93 | 95 | 96 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Luciano Marisi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Metadata/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Metadata/JSONUtilities.h: -------------------------------------------------------------------------------- 1 | // 2 | // JSONUtilities.h 3 | // JSONUtilities 4 | // 5 | // Created by Luciano Marisi on 21/11/2015. 6 | // Copyright © 2015 Luciano Marisi All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for JSONUtilities. 12 | FOUNDATION_EXPORT double JSONUtilitiesVersionNumber; 13 | 14 | //! Project version string for JSONUtilities. 15 | FOUNDATION_EXPORT const unsigned char JSONUtilitiesVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /NonSwiftPackageManagerTests/BundleFileLoadingTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BundleFileLoadingTests.swift 3 | // JSONUtilities 4 | // 5 | // Created by Luciano Marisi on 17/07/2016. 6 | // Copyright © 2016 Luciano Marisi All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import JSONUtilities 11 | 12 | class BundleFileLoadingTests: XCTestCase { 13 | 14 | func testLoadFromBundle() { 15 | do { 16 | let _ = try JSONDictionary.from(filename: JSONFilename.correct, bundle: testBundle) 17 | } catch { 18 | XCTFail("Unexpected error: \(error)") 19 | } 20 | } 21 | 22 | func testAttemptToLoadMissingFileFromBundle() { 23 | do { 24 | let _ = try JSONDictionary.from(filename: JSONFilename.missing, bundle: testBundle) 25 | } catch let error as JSONUtilsError { 26 | XCTAssertEqual(error, JSONUtilsError.couldNotFindFile) 27 | } catch { 28 | XCTFail("Unexpected error: \(error)") 29 | } 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:4.0 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "JSONUtilities", 6 | products: [ 7 | .library( 8 | name: "JSONUtilities", 9 | targets: ["JSONUtilities"]), 10 | ], 11 | dependencies: [], 12 | targets: [ 13 | .target( 14 | name: "JSONUtilities", 15 | dependencies: []), 16 | .testTarget( 17 | name: "JSONUtilitiesTests", 18 | dependencies: ["JSONUtilities"]), 19 | ] 20 | ) 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JSONUtilities 2 | 3 | [![Build Status](https://travis-ci.org/lucianomarisi/JSONUtilities.svg?branch=master)](https://travis-ci.org/lucianomarisi/JSONUtilities) 4 | [![](https://img.shields.io/cocoapods/v/JSONUtilities.svg)](https://cocoapods.org/pods/JSONUtilities) 5 | [![](https://img.shields.io/cocoapods/p/JSONUtilities.svg)](https://cocoapods.org/pods/JSONUtilities) 6 | [![codecov.io](http://codecov.io/github/lucianomarisi/JSONUtilities/coverage.svg?branch=master)](http://codecov.io/github/lucianomarisi/JSONUtilities?branch=master) 7 | ![Carthage Compatible](https://img.shields.io/badge/carthage-compatible-4BC51D.svg?style=flat) 8 | ![Swift Version](https://img.shields.io/badge/swift-3.0-brightgreen.svg) 9 | 10 | Easily load JSON objects and decode them into structs or classes. The `json(atKeyPath:)` function infers the type from the constant or variable definition to decode meaning no casting is needed. Both string keys and keypaths (keys separated by dots `.`) are supported when decoding JSON. 11 | 12 | - Check out the `Example.playground` inside the `JSONUtilities.xcodeproj` for a working example 13 | 14 | ## Installation 15 | 16 | **CocoaPods:** 17 | 18 | Add the line `pod 'JSONUtilities'` to your `Podfile`. 19 | 20 | **Carthage:** 21 | 22 | Add the line `github "lucianomarisi/JSONUtilities"` to your `Cartfile`. 23 | 24 | **Manual:** 25 | 26 | Add the files inside the `Sources` folder into your Xcode project. 27 | 28 | **Swift Package Manager:** 29 | 30 | Add the line `.Package(url: "https://github.com/lucianomarisi/JSONUtilities", majorVersion: 3)` to your `Package.swift`. 31 | 32 | ## Types supported 33 | 34 | ### JSON raw types: 35 | 36 | - `Int` 37 | - `Double` 38 | - `Float` 39 | - `String` 40 | - `Bool` 41 | - `[String: AnyObject]` 42 | - `RawRepresentable` enums 43 | 44 | ### Array of JSON raw types: 45 | 46 | - `[Int]` 47 | - `[Double]` 48 | - `[Float]` 49 | - `[String]` 50 | - `[Bool]` 51 | - `[[String: AnyObject]]` 52 | - `[RawRepresentable]` 53 | 54 | ### Custom JSON objects and custom JSON object arrays 55 | 56 | e.g. if `MyClass` and `MyStruct` conform to `JSONObjectConvertible` protocol 57 | 58 | - `MyClass` 59 | - [`MyClass`] 60 | - `MyStruct` 61 | - [`MyStruct`] 62 | 63 | ### Typed dictionaries with a `String` key 64 | 65 | - `[String: JSONRawType]` 66 | - `[String: JSONObjectConvertible]` 67 | - `[String: JSONPrimitiveConvertible]` 68 | - `[String: RawRepresentable]` 69 | 70 | ## InvalidItemBehaviour 71 | 72 | When decoding arrays or dictionaries an `invalidItemBehaviour` parameter can be passed which controls what happens when an error occurs while decoding child items 73 | 74 | - `.remove` this will simply remove the item from the array or dictionary. This is the default 75 | - `.fail` if any of the children encounter an error the whole array or dictionary decoding will fail. For optional properties this means the array or dictionary will return nil, and for non optional properties it will throw an error 76 | - `.value(T)` Provide an alternative value 77 | - `.custom((DecodingError) -> InvalidItemBehaviour)` Lets you specify the behaviour based on the specific DecodingError 78 | 79 | ## Examples of JSON loading 80 | 81 | ### From file 82 | 83 | ```swift 84 | let filename = "myjsonfile" 85 | let dictionary: [String: AnyObject] = try JSONDictionary.from(filename: filename) 86 | ``` 87 | 88 | ### From data 89 | 90 | ```swift 91 | let data: Data = ... 92 | let dictionary: [String: AnyObject] = try JSONDictionary.from(jsonData: data) 93 | ``` 94 | 95 | ## Examples of JSON decoding 96 | 97 | Consider a JSON object that represents a person: 98 | 99 | ```json 100 | { 101 | "name" : "John Doe", 102 | "age" : 24, 103 | "weight" : 72.4 104 | } 105 | ``` 106 | 107 | ### Decode JSON inline 108 | 109 | ```swift 110 | let jsonDictionary = try JSONDictionary.from(filename: "person.json") 111 | let name: String = try jsonDictionary.json(atKeyPath: "name") 112 | let age: Int = try jsonDictionary.json(atKeyPath: "age") 113 | let weight: Int = try jsonDictionary.json(atKeyPath: "weight") 114 | let profession: String? = jsonDictionary.json(atKeyPath: "profession") // Optional decoding 115 | ``` 116 | 117 | ### Decode structs or classes 118 | 119 | ```swift 120 | struct Person { //OR class Person { 121 | 122 | let name: String 123 | let age: Int 124 | let weight: Double 125 | let profession: String? 126 | 127 | init(jsonDictionary: JSONDictionary) throws { 128 | name = try jsonDictionary.json(atKeyPath: "name") 129 | age = try jsonDictionary.json(atKeyPath: "age") 130 | weight = try jsonDictionary.json(atKeyPath: "weight") 131 | profession = jsonDictionary.json(atKeyPath: "profession") 132 | } 133 | 134 | } 135 | ``` 136 | 137 | ### Decode nested structs or classes by conforming to the JSONObjectConvertible protocol 138 | 139 | Consider a company JSON object: 140 | 141 | ```json 142 | { 143 | "name" : "Working name LTD.", 144 | "employees": [ 145 | { 146 | "name": "John Doe", 147 | "age": 24, 148 | "weight": 72.4 149 | }, 150 | { 151 | "name": "Jane Doe", 152 | "age": 22, 153 | "weight": 70.1 154 | } 155 | ] 156 | } 157 | ``` 158 | 159 | The `Company` struct can decode an array of `Person` structs/classes by making `Person` conform to the `JSONObjectConvertible` protocol 160 | 161 | ```swift 162 | struct Company { 163 | let name: String 164 | let employees: [Person] 165 | 166 | init(jsonDictionary: JSONDictionary) throws { 167 | name = try jsonDictionary.json(atKeyPath: "name") 168 | employees = try jsonDictionary.json(atKeyPath: "employees") 169 | } 170 | } 171 | ``` 172 | 173 | ### Support custom primitive types by conforming to `JSONPrimitiveConvertible` 174 | 175 | Any type can extend the `JSONPrimitiveConvertible` protocol in order to allow decoding. For example extending `URL`: **Note that this extension come out of the box** 176 | : 177 | 178 | ```swift 179 | extension URL: JSONPrimitiveConvertible { 180 | 181 | public typealias JSONType = String 182 | 183 | public static func from(jsonValue: String) -> Self? { 184 | return self.init(string: jsonValue) 185 | } 186 | 187 | } 188 | 189 | let urlDictionary = ["url": "www.google.com"] 190 | let url: URL = try! urlDictionary.json(atKeyPath: "url") // www.google.com 191 | ``` 192 | 193 | 194 | It's also possible to have an array of `JSONPrimitiveConvertible` values, for example: 195 | 196 | ```swift 197 | let urlsDictionary = ["urls": ["www.google.com", "www.yahoo.com"]] 198 | let urls: [URL] = try! urlsDictionary.json(atKeyPath: "urls") // [www.google.com, www.yahoo.com] 199 | ``` 200 | -------------------------------------------------------------------------------- /Sources/JSONUtilities/DecodingError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Errors.swift 3 | // JSONUtilities 4 | // 5 | // Created by Luciano Marisi on 05/03/2016. 6 | // Copyright © 2016 Luciano Marisi All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | Error that occurs when decoding 13 | */ 14 | public struct DecodingError: Error, CustomStringConvertible, CustomDebugStringConvertible { 15 | 16 | /// The reason the error occurred 17 | public var reason: Reason 18 | 19 | /// dictionary in which the error occured 20 | public let dictionary: [AnyHashable: Any] 21 | 22 | /// array in which the error occurred 23 | public let array: JSONArray? 24 | 25 | /// the keypath at which the error occurred 26 | public let keyPath: String 27 | 28 | /// the expected type that was being decoded while the error happened 29 | public let expectedType: Any 30 | 31 | /// the value that caused the error 32 | public let value: Any 33 | 34 | public init(dictionary: [AnyHashable: Any], keyPath: StringProtocol, expectedType: Any.Type, value: Any, array: JSONArray? = nil, reason: Reason) { 35 | self.dictionary = dictionary 36 | self.keyPath = keyPath.string 37 | self.expectedType = expectedType 38 | self.value = value 39 | self.reason = reason 40 | self.array = array 41 | } 42 | 43 | var reasonDescription: String { 44 | switch reason { 45 | case .keyNotFound: 46 | return "Nothing found" 47 | case .incorrectRawRepresentableRawValue: 48 | return "Incorrect rawValue of \(value) for \(expectedType)" 49 | case .incorrectType: 50 | return "Incorrect type, expected \(expectedType) found \(value)" 51 | case .conversionFailure: 52 | return "\(expectedType) failed to convert \(value)" 53 | } 54 | } 55 | 56 | public var description: String { 57 | return "Decoding failed at \"\(keyPath)\": \(reasonDescription)" 58 | } 59 | 60 | public var debugDescription: String { 61 | return "\(description):\n\(array?.description ?? dictionary.description)" 62 | } 63 | 64 | /// The reason the error occurred 65 | public enum Reason: String, CustomStringConvertible { 66 | 67 | /// Key was not found in a JSONDictionary 68 | case keyNotFound = "Key not found" 69 | 70 | /// A value was found that can't initialise a RawRepresentable. In the case of an enum, the rawValue did not match any of the case's rawValue 71 | case incorrectRawRepresentableRawValue = "Incorrect RawRepresentable RawValue" 72 | 73 | /// The value has the incorrect type 74 | case incorrectType = "Incorrect type" 75 | 76 | /// A JSONPrimitiveConvertible failed to convert 77 | case conversionFailure = "Conversion failure" 78 | 79 | public var description: String { 80 | return rawValue 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /Sources/JSONUtilities/Dictionary+JSONKey.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Dictionary+JSONKey.swift 3 | // JSONUtilities 4 | // 5 | // Created by Luciano Marisi on 05/03/2016. 6 | // Copyright © 2016 Luciano Marisi All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// Protocol used for defining the valid JSON types, i.e. Int, Double, Float, String and Bool 12 | public protocol JSONRawType {} 13 | extension Int : JSONRawType {} 14 | extension Double : JSONRawType {} 15 | extension Float : JSONRawType {} 16 | extension String : JSONRawType {} 17 | extension Bool : JSONRawType {} 18 | 19 | // Simple protocol used to extend a JSONDictionary 20 | public protocol StringProtocol { 21 | func components(separatedBy: String) -> [String] 22 | var string: String { get } 23 | } 24 | extension String: StringProtocol { 25 | public var string: String { 26 | return self 27 | } 28 | } 29 | 30 | extension Dictionary where Key: StringProtocol { 31 | 32 | // MARK: JSONRawType type 33 | 34 | /// Decode a mandatory JSON raw type 35 | public func json(atKeyPath keyPath: Key) throws -> T { 36 | return try getValue(atKeyPath: keyPath) 37 | } 38 | 39 | /// Decode an optional JSON raw type 40 | public func json(atKeyPath keyPath: Key) -> T? { 41 | return try? json(atKeyPath: keyPath) as T 42 | } 43 | 44 | // MARK: [JSONRawType] type 45 | 46 | /// Decode an Array of mandatory JSON raw types 47 | public func json(atKeyPath keyPath: Key, invalidItemBehaviour: InvalidItemBehaviour = .remove) throws -> [T] { 48 | return try decodeArray(atKeyPath: keyPath, invalidItemBehaviour: invalidItemBehaviour, decode: getValue) 49 | } 50 | 51 | /// Decode an Array of optional JSON raw types 52 | public func json(atKeyPath keyPath: Key, invalidItemBehaviour: InvalidItemBehaviour = .remove) -> [T]? { 53 | return try? self.json(atKeyPath: keyPath, invalidItemBehaviour: invalidItemBehaviour) as [T] 54 | } 55 | 56 | // MARK: [String: Any] type 57 | 58 | /// Decodes as a raw Dictionary with a mandatory key 59 | public func json(atKeyPath keyPath: Key) throws -> JSONDictionary { 60 | return try getValue(atKeyPath: keyPath) 61 | } 62 | 63 | /// Decodes as a raw dictionary with an optional key 64 | public func json(atKeyPath keyPath: Key) -> JSONDictionary? { 65 | return self[keyPath: keyPath] as? JSONDictionary 66 | } 67 | 68 | // MARK: [[String: Any]] type 69 | 70 | /// Decodes as a raw dictionary array with a mandatory key 71 | public func json(atKeyPath keyPath: Key, invalidItemBehaviour: InvalidItemBehaviour = .remove) throws -> [JSONDictionary] { 72 | return try decodeArray(atKeyPath: keyPath, invalidItemBehaviour: invalidItemBehaviour, decode: getValue) 73 | } 74 | 75 | /// Decodes as a raw ictionary array with an optional key 76 | public func json(atKeyPath keyPath: Key, invalidItemBehaviour: InvalidItemBehaviour = .remove) -> [JSONDictionary]? { 77 | return try? self.json(atKeyPath: keyPath, invalidItemBehaviour: invalidItemBehaviour) as [JSONDictionary] 78 | } 79 | 80 | // MARK: [String: JSONObjectConvertible] type 81 | 82 | /// Decodes a mandatory dictionary 83 | public func json(atKeyPath keyPath: Key, invalidItemBehaviour: InvalidItemBehaviour = .remove) throws -> [String: T] { 84 | return try decodeDictionary(atKeyPath: keyPath, invalidItemBehaviour: invalidItemBehaviour) { jsonDictionary, key in 85 | return try jsonDictionary.json(atKeyPath: key) as T 86 | } 87 | } 88 | 89 | /// Decodes an optional dictionary 90 | public func json(atKeyPath keyPath: Key, invalidItemBehaviour: InvalidItemBehaviour = .remove) -> [String: T]? { 91 | return try? json(atKeyPath: keyPath, invalidItemBehaviour: invalidItemBehaviour) as [String: T] 92 | } 93 | 94 | // MARK: [String: JSONRawType] type 95 | 96 | /// Decodes a mandatory dictionary 97 | public func json(atKeyPath keyPath: Key, invalidItemBehaviour: InvalidItemBehaviour = .remove) throws -> [String: T] { 98 | return try decodeDictionary(atKeyPath: keyPath, invalidItemBehaviour: invalidItemBehaviour) { jsonDictionary, key in 99 | return try jsonDictionary.json(atKeyPath: key) as T 100 | } 101 | } 102 | 103 | /// Decodes an optional dictionary 104 | public func json(atKeyPath keyPath: Key, invalidItemBehaviour: InvalidItemBehaviour = .remove) -> [String: T]? { 105 | return try? json(atKeyPath: keyPath, invalidItemBehaviour: invalidItemBehaviour) as [String: T] 106 | } 107 | 108 | // MARK: [String: JSONPrimitiveConvertible] type 109 | 110 | /// Decodes a mandatory dictionary 111 | public func json(atKeyPath keyPath: Key, invalidItemBehaviour: InvalidItemBehaviour = .remove) throws -> [String: T] { 112 | return try decodeDictionary(atKeyPath: keyPath, invalidItemBehaviour: invalidItemBehaviour) { jsonDictionary, key in 113 | return try jsonDictionary.json(atKeyPath: key) as T 114 | } 115 | } 116 | 117 | /// Decodes an optional dictionary 118 | public func json(atKeyPath keyPath: Key, invalidItemBehaviour: InvalidItemBehaviour = .remove) -> [String: T]? { 119 | return try? json(atKeyPath: keyPath, invalidItemBehaviour: invalidItemBehaviour) as [String: T] 120 | } 121 | 122 | // MARK: Decodable types 123 | 124 | /// Decode a mandatory Decodable object 125 | public func json(atKeyPath keyPath: Key) throws -> T { 126 | return try T(jsonDictionary: JSONDictionaryForKey(atKeyPath: keyPath)) 127 | } 128 | 129 | /// Decode an optional Decodable object 130 | public func json(atKeyPath keyPath: Key) -> T? { 131 | return try? T(jsonDictionary: JSONDictionaryForKey(atKeyPath: keyPath)) 132 | } 133 | 134 | // MARK: [Decodable] types 135 | 136 | /// Decode an Array of mandatory Decodable objects 137 | public func json(atKeyPath keyPath: Key, invalidItemBehaviour: InvalidItemBehaviour = .remove) throws -> [T] { 138 | return try decodeArray(atKeyPath: keyPath, invalidItemBehaviour: invalidItemBehaviour) { keyPath, jsonArray, value in 139 | let jsonDictionary: JSONDictionary = try getValue(atKeyPath: keyPath, array: jsonArray, value: value) 140 | return try T(jsonDictionary: jsonDictionary) 141 | } 142 | } 143 | 144 | /// Decode an Array of optional Decodable objects 145 | public func json(atKeyPath keyPath: Key, invalidItemBehaviour: InvalidItemBehaviour = .remove) -> [T]? { 146 | return try? json(atKeyPath: keyPath, invalidItemBehaviour: invalidItemBehaviour) as [T] 147 | } 148 | 149 | // MARK: RawRepresentable type 150 | 151 | /// Decode a mandatory RawRepresentable 152 | public func json(atKeyPath keyPath: Key) throws -> T where T.RawValue:JSONRawType { 153 | let rawValue: T.RawValue = try getValue(atKeyPath: keyPath) 154 | 155 | guard let value = T(rawValue:rawValue) else { 156 | throw DecodingError(dictionary: self, keyPath: keyPath, expectedType: T.self, value: rawValue, reason: .incorrectRawRepresentableRawValue) 157 | } 158 | 159 | return value 160 | } 161 | 162 | /// Decode an optional RawRepresentable 163 | public func json(atKeyPath keyPath: Key) -> T? where T.RawValue:JSONRawType { 164 | return try? json(atKeyPath: keyPath) as T 165 | } 166 | 167 | // MARK: [RawRepresentable] type 168 | 169 | /// Decode an array of custom RawRepresentable types with a mandatory key 170 | public func json(atKeyPath keyPath: Key, invalidItemBehaviour: InvalidItemBehaviour = .remove) throws -> [T] where T.RawValue:JSONRawType { 171 | 172 | return try decodeArray(atKeyPath: keyPath, invalidItemBehaviour: invalidItemBehaviour) { keyPath, jsonArray, value in 173 | let rawValue: T.RawValue = try getValue(atKeyPath: keyPath, array: jsonArray, value: value) 174 | 175 | guard let value = T(rawValue:rawValue) else { 176 | throw DecodingError(dictionary: self, keyPath: keyPath, expectedType: T.self, value: rawValue, array: jsonArray, reason: .incorrectRawRepresentableRawValue) 177 | } 178 | return value 179 | } 180 | } 181 | 182 | /// Optionally decode an array of RawRepresentable types with a mandatory key 183 | public func json(atKeyPath keyPath: Key, invalidItemBehaviour: InvalidItemBehaviour = .remove) -> [T]? where T.RawValue:JSONRawType { 184 | return try? json(atKeyPath: keyPath, invalidItemBehaviour: invalidItemBehaviour) as [T] 185 | } 186 | 187 | // MARK: [String: RawRepresentable] type 188 | 189 | /// Decode a dictionary of custom RawRepresentable types with a mandatory key 190 | public func json(atKeyPath keyPath: Key, invalidItemBehaviour: InvalidItemBehaviour = .remove) throws -> [String: T] where T.RawValue:JSONRawType { 191 | return try decodeDictionary(atKeyPath: keyPath, invalidItemBehaviour: invalidItemBehaviour) { jsonDictionary, key in 192 | let rawValue: T.RawValue = try jsonDictionary.getValue(atKeyPath: key) 193 | 194 | guard let value = T(rawValue:rawValue) else { 195 | throw DecodingError(dictionary: jsonDictionary, keyPath: keyPath, expectedType: T.self, value: rawValue, reason: .incorrectRawRepresentableRawValue) 196 | } 197 | return value 198 | } 199 | } 200 | 201 | /// Optionally decode a dictionary of RawRepresentable types with a mandatory key 202 | public func json(atKeyPath keyPath: Key, invalidItemBehaviour: InvalidItemBehaviour = .remove) -> [String: T]? where T.RawValue:JSONRawType { 203 | return try? json(atKeyPath: keyPath, invalidItemBehaviour: invalidItemBehaviour) as [String: T] 204 | } 205 | 206 | // MARK: JSONPrimitiveConvertible type 207 | 208 | /// Decode a custom raw types with a mandatory key 209 | public func json(atKeyPath keyPath: Key) throws -> T { 210 | let jsonValue: T.JSONType = try getValue(atKeyPath: keyPath) 211 | 212 | guard let transformedValue = T.from(jsonValue: jsonValue) else { 213 | throw DecodingError(dictionary: self, keyPath: keyPath, expectedType: T.self, value: jsonValue, reason: .conversionFailure) 214 | } 215 | 216 | return transformedValue 217 | } 218 | 219 | /// Optionally decode a custom raw types with a mandatory key 220 | public func json(atKeyPath keyPath: Key) -> T? { 221 | return try? json(atKeyPath: keyPath) as T 222 | } 223 | 224 | // MARK: [JSONPrimitiveConvertible] type 225 | 226 | /// Decode an array of custom raw types with a mandatory key 227 | public func json(atKeyPath keyPath: Key, invalidItemBehaviour: InvalidItemBehaviour = .remove) throws -> [T] { 228 | return try decodeArray(atKeyPath: keyPath, invalidItemBehaviour: invalidItemBehaviour) { keyPath, jsonArray, value in 229 | let jsonValue: T.JSONType = try getValue(atKeyPath: keyPath, array: jsonArray, value: value) 230 | 231 | guard let transformedValue = T.from(jsonValue: jsonValue) else { 232 | throw DecodingError(dictionary: self, keyPath: keyPath, expectedType: T.self, value: jsonValue, array: jsonArray, reason: .conversionFailure) 233 | } 234 | return transformedValue 235 | } 236 | } 237 | 238 | /// Optionally decode an array custom raw types with a mandatory key 239 | public func json(atKeyPath keyPath: Key, invalidItemBehaviour: InvalidItemBehaviour = .remove) -> [T]? { 240 | return try? json(atKeyPath: keyPath, invalidItemBehaviour: invalidItemBehaviour) as [T] 241 | } 242 | 243 | // MARK: JSONDictionary and JSONArray creation 244 | 245 | fileprivate func JSONDictionaryForKey(atKeyPath keyPath: Key) throws -> JSONDictionary { 246 | return try getValue(atKeyPath: keyPath) 247 | } 248 | 249 | fileprivate func JSONArrayForKey(atKeyPath keyPath: Key) throws -> JSONArray { 250 | return try getValue(atKeyPath: keyPath) 251 | } 252 | 253 | // MARK: Value decoding 254 | 255 | fileprivate func getValue(atKeyPath keyPath: Key, array: [A], value: A) throws -> B { 256 | guard let typedValue = value as? B else { 257 | throw DecodingError(dictionary: self, keyPath: keyPath, expectedType: B.self, value: value, array: array, reason: .incorrectType) 258 | } 259 | return typedValue 260 | } 261 | 262 | fileprivate func getValue(atKeyPath keyPath: Key) throws -> T { 263 | guard let value = self[keyPath: keyPath] else { 264 | throw DecodingError(dictionary: self, keyPath: keyPath, expectedType: T.self, value: "", reason: .keyNotFound) 265 | } 266 | guard let typedValue = value as? T else { 267 | throw DecodingError(dictionary: self, keyPath: keyPath, expectedType: T.self, value: value, reason: .incorrectType) 268 | } 269 | return typedValue 270 | } 271 | 272 | // MARK: Dictionary decoding 273 | 274 | fileprivate func decodeDictionary(atKeyPath keyPath: Key, invalidItemBehaviour: InvalidItemBehaviour = .remove, decode: (JSONDictionary, String) throws -> T) throws -> [String: T] { 275 | let jsonDictionary: JSONDictionary = try json(atKeyPath: keyPath) 276 | 277 | var dictionary: [String: T] = [:] 278 | for (key, _) in jsonDictionary { 279 | if let item = try invalidItemBehaviour.decodeItem(decode: { try decode(jsonDictionary, key) }) { 280 | dictionary[key] = item 281 | } 282 | } 283 | 284 | return dictionary 285 | } 286 | 287 | // MARK: Array decoding 288 | 289 | fileprivate func decodeArray(atKeyPath keyPath: Key, invalidItemBehaviour: InvalidItemBehaviour = .remove, decode: (Key, JSONArray, Any) throws -> T) throws -> [T] { 290 | let jsonArray = try JSONArrayForKey(atKeyPath: keyPath) 291 | return try jsonArray.compactMap { value in 292 | try invalidItemBehaviour.decodeItem(decode: { try decode(keyPath, jsonArray, value) }) 293 | } 294 | } 295 | 296 | } 297 | -------------------------------------------------------------------------------- /Sources/JSONUtilities/Dictionary+KeyPath.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Dictionary+KeyPath.swift 3 | // JSONUtilities 4 | // 5 | // Created by Luciano Marisi on 04/09/2016. 6 | // Copyright © 2016 Luciano Marisi All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Dictionary { 12 | 13 | /// Retrieves a value for a keyPath on the dictionary 14 | /// 15 | /// - parameter keyPath: A string of keys separated by dots 16 | /// 17 | /// - returns: Optionally returns a generic value for this keyPath or nil 18 | subscript(keyPath keyPath: StringProtocol) -> Any? { 19 | var keys = keyPath.components(separatedBy: ".") 20 | guard let firstKey = keys.first as? Key, 21 | let value = self[firstKey] 22 | else { return nil } 23 | 24 | keys.removeFirst() 25 | 26 | if !keys.isEmpty, let subDictionary = value as? [Key : Any] { 27 | let rejoined = keys.joined(separator: ".") 28 | return subDictionary[keyPath: rejoined] 29 | } 30 | return value 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /Sources/JSONUtilities/InvalidItemBehaviour.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InvalidItemBehaviour.swift 3 | // JSONUtilities 4 | // 5 | // Created by Yonas Kolb on 27/4/17. 6 | // Copyright © 2017 Luciano Marisi. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// The behaviour of what should be done when an invalid JSON object or primitive is found 12 | /// 13 | /// - remove: The item is filtered, only valid items are returned 14 | /// - fail: The call fails. For non optional properties this will throw an error, and for optional properties nil is returned 15 | public enum InvalidItemBehaviour { 16 | case remove 17 | case fail 18 | case value(T) 19 | case custom((DecodingError) -> InvalidItemBehaviour) 20 | 21 | func decodeItem(decode: () throws -> T) throws -> T? { 22 | do { 23 | return try decode() 24 | } catch { 25 | let decodingError = error as? DecodingError 26 | 27 | switch self { 28 | case .remove: 29 | return nil 30 | case .fail: 31 | throw error 32 | case .value(let value): 33 | return value 34 | case .custom(let getBehaviour): 35 | guard let decodingError = decodingError else { return nil } 36 | let behaviour = getBehaviour(decodingError) 37 | return try behaviour.decodeItem(decode: decode) 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Sources/JSONUtilities/JSONFileLoading.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JSONFileLoading.swift 3 | // JSONUtilities 4 | // 5 | // Created by Luciano Marisi on 21/11/2015. 6 | // Copyright © 2015 Luciano Marisi All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public typealias JSONDictionary = [String : Any] 12 | public typealias JSONArray = [Any] 13 | 14 | /** 15 | Loading .json file error 16 | 17 | - FileLoadingFailed: The .json file URL could not be found or the file could not be loaded 18 | - FileDeserializationFailed: NSJSONSerialization failed to deserialize the file 19 | - FileNotAJSONDictionary: The .json does not contain a JSON object (i.e [String: Any]) as a top level object 20 | */ 21 | public enum JSONUtilsError: Error { 22 | case couldNotFindFile 23 | case fileLoadingFailed 24 | case fileDeserializationFailed 25 | case fileNotAJSONDictionary 26 | } 27 | 28 | public extension Dictionary where Key: StringProtocol, Value: Any { 29 | 30 | /** 31 | Load a JSONDictionary from a file 32 | 33 | - parameter filename: The filename of the JSON file 34 | 35 | - throws: Throws if a JSONDictionary cannot be created from the file 36 | 37 | - returns: An initilized JSONDictionary 38 | */ 39 | static func from(filename: String, bundle: Bundle = .main) throws -> JSONDictionary { 40 | bundle.url(forResource: filename, withExtension: "json") 41 | guard let url = bundle.url(forResource: filename, withExtension: "json") else { 42 | throw JSONUtilsError.couldNotFindFile 43 | } 44 | return try from(url: url) 45 | } 46 | 47 | /** 48 | Load a JSONDictionary from a file 49 | 50 | - parameter url: The url of the json file 51 | - parameter bundle: The NSBundle to be used 52 | 53 | - throws: Throws if a JSONDictionary cannot be created from the file 54 | 55 | - returns: An initilized JSONDictionary 56 | */ 57 | @discardableResult 58 | static func from(url: URL) throws -> JSONDictionary { 59 | guard let jsonData = try? Data(contentsOf: url) else { 60 | throw JSONUtilsError.fileLoadingFailed 61 | } 62 | return try from(jsonData: jsonData) 63 | } 64 | 65 | /** 66 | Load a JSONDictionary from a NSData object 67 | 68 | - parameter jsonData: The JSON NSData to deserialize 69 | 70 | - throws: Throws if a JSONDictionary cannot be created from the file 71 | 72 | - returns: An initilized JSONDictionary 73 | */ 74 | static func from(jsonData: Data) throws -> JSONDictionary { 75 | guard let deserializedJSON = try? JSONSerialization.jsonObject(with: jsonData, options: JSONSerialization.ReadingOptions.mutableContainers) else { 76 | throw JSONUtilsError.fileDeserializationFailed 77 | } 78 | 79 | guard let jsonDictionary: JSONDictionary = deserializedJSON as? JSONDictionary else { 80 | throw JSONUtilsError.fileNotAJSONDictionary 81 | } 82 | return jsonDictionary 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Sources/JSONUtilities/JSONObjectConvertible.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JSONObjectConvertible.swift 3 | // JSONUtilities 4 | // 5 | // Created by Luciano Marisi on 05/03/2016. 6 | // Copyright © 2016 Luciano Marisi All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | * Use the Decodable protocol to support nested JSON objects 13 | */ 14 | public protocol JSONObjectConvertible { 15 | /** 16 | Creates a instance of struct or class from a JSONDictionary 17 | 18 | - parameter jsonDictionary: The JSON dictionary to parse 19 | 20 | - throws: Throws a decoding error if decoding failed 21 | 22 | - returns: A decoded instance of the type conforming to the protocol 23 | */ 24 | init(jsonDictionary: JSONDictionary) throws 25 | } 26 | -------------------------------------------------------------------------------- /Sources/JSONUtilities/JSONPrimitiveConvertible.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JSONPrimitiveConvertible.swift 3 | // JSONUtilities 4 | // 5 | // Created by Luciano Marisi on 13/03/2016. 6 | // Copyright © 2016 Luciano Marisi All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | * Protocol used to support raw primitive types other than object types, e.g. NSURL 13 | */ 14 | public protocol JSONPrimitiveConvertible { 15 | 16 | /// The type of the raw JSON value that will be decoded to be transformed 17 | associatedtype JSONType: JSONRawType 18 | 19 | /** 20 | Create a type from a JSON value 21 | 22 | - parameter jsonValue: The value to transform 23 | 24 | - returns: An initialized type if succesful or nil 25 | */ 26 | static func from(jsonValue: JSONType) -> Self? 27 | } 28 | -------------------------------------------------------------------------------- /Sources/JSONUtilities/URL+JSONPrimitiveConvertible.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URL+JSONPrimitiveConvertible.swift 3 | // JSONUtilities 4 | // 5 | // Created by Sam Dods on 14/09/2016. 6 | // Copyright © 2016 Luciano Marisi. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension URL: JSONPrimitiveConvertible { 12 | 13 | public typealias JSONType = String 14 | 15 | /// Creates a URL from a string, in order to conform to JSONPrimitiveConvertible 16 | /// 17 | /// - parameter jsonValue: The string representation of a valid URL 18 | /// 19 | /// - returns: Returns a URL if the input string was successfully converted to a URL 20 | public static func from(jsonValue: String) -> URL? { 21 | return URL(string: jsonValue) 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /Tests/.swiftlint.yml: -------------------------------------------------------------------------------- 1 | disabled_rules: 2 | - line_length 3 | - force_try 4 | - force_cast 5 | - type_name 6 | - function_body_length 7 | - redundant_discardable_let 8 | -------------------------------------------------------------------------------- /Tests/JSONUtilitiesTests/Dictionary+KeyPathTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Dictionary+KeyPathTests.swift 3 | // JSONUtilities 4 | // 5 | // Created by Luciano Marisi on 04/09/2016. 6 | // Copyright © 2016 Luciano Marisi. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import JSONUtilities 11 | 12 | class Dictionary_KeyPathTests: XCTestCase { 13 | 14 | func testAccessOneLevelDeep() { 15 | let expectedValue = "value" 16 | let dictionary = [ 17 | "root_key": [ 18 | "first_level_key": expectedValue 19 | ] 20 | ] 21 | guard let calculatedValue = dictionary[keyPath: "root_key.first_level_key"] as? String else { 22 | XCTFail(#function) 23 | return 24 | } 25 | XCTAssertEqual(calculatedValue, expectedValue) 26 | } 27 | 28 | func testAccessTwoLevelDeep() { 29 | let expectedValue = "value" 30 | let dictionary = [ 31 | "root_key": [ 32 | "first_level_key": [ 33 | "second_level_key": expectedValue 34 | ] 35 | ] 36 | ] 37 | guard let calculatedValue = dictionary[keyPath: "root_key.first_level_key.second_level_key"] as? String else { 38 | XCTFail(#function) 39 | return 40 | } 41 | XCTAssertEqual(calculatedValue, expectedValue) 42 | } 43 | 44 | func testAccessRepeatedKey_InSequence_OneLevelDeep() { 45 | let expectedValue = "value" 46 | let dictionary = [ 47 | "root_key": [ 48 | "root_key": expectedValue 49 | ] 50 | ] 51 | guard let calculatedValue = dictionary[keyPath: "root_key.root_key"] as? String else { 52 | XCTFail(#function) 53 | return 54 | } 55 | XCTAssertEqual(calculatedValue, expectedValue) 56 | } 57 | 58 | func testAccessRepeatedKey_InSequence_TwoLevelsDeep() { 59 | let expectedValue = "value" 60 | let dictionary = [ 61 | "root_key": [ 62 | "root_key": [ 63 | "root_key": expectedValue 64 | ] 65 | ] 66 | ] 67 | guard let calculatedValue = dictionary[keyPath: "root_key.root_key.root_key"] as? String else { 68 | XCTFail(#function) 69 | return 70 | } 71 | XCTAssertEqual(calculatedValue, expectedValue) 72 | } 73 | 74 | func testAccessRepeatedKey_InBetweenSequence_TwoLevelsDeep() { 75 | let expectedValue = "value" 76 | let dictionary = [ 77 | "root_key": [ 78 | "first_level_key": [ 79 | "root_key": expectedValue 80 | ] 81 | ] 82 | ] 83 | guard let calculatedValue = dictionary[keyPath: "root_key.first_level_key.root_key"] as? String else { 84 | XCTFail(#function) 85 | return 86 | } 87 | XCTAssertEqual(calculatedValue, expectedValue) 88 | } 89 | 90 | func testAccess_MalformedKeyPath_OneLevelDeep() { 91 | let expectedValue = "value" 92 | let dictionary = [ 93 | "root_key": [ 94 | "first_level_key": expectedValue 95 | ] 96 | ] 97 | 98 | let calculatedValue = dictionary[keyPath: "root_key..first_level_key"] as? String 99 | XCTAssertNil(calculatedValue) 100 | } 101 | 102 | func testAccess_KeyPathEndsWithADot_OneLevelDeep() { 103 | let expectedValue = "value" 104 | let dictionary = [ 105 | "root_key": [ 106 | "first_level_key": expectedValue 107 | ] 108 | ] 109 | 110 | guard let calculatedValue = dictionary[keyPath: "root_key.first_level_key."] as? String else { 111 | XCTFail(#function) 112 | return 113 | } 114 | XCTAssertEqual(calculatedValue, expectedValue) 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /Tests/JSONUtilitiesTests/FileLoadingTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FileLoadingTests.swift 3 | // FileLoadingTests 4 | // 5 | // Created by Luciano Marisi on 21/11/2015. 6 | // Copyright © 2015 Luciano Marisi All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import JSONUtilities 11 | 12 | typealias NoEscapeFunction = ( () throws -> Void) 13 | 14 | class FileLoadingTests: XCTestCase { 15 | 16 | func testLoadingJSONFile() { 17 | do { 18 | try JSONDictionary.from(url: JSONFilePath.correct) 19 | } catch { 20 | XCTFail("Unexpected error: \(error)") 21 | } 22 | } 23 | 24 | func testLoadingJSONFileLoadingFailed() { 25 | expectError(.fileLoadingFailed) { 26 | try JSONDictionary.from(url: JSONFilePath.missing) 27 | } 28 | } 29 | 30 | func testLoadingJSONFileDeserializationFailed() { 31 | expectError(.fileDeserializationFailed) { 32 | try JSONDictionary.from(url: JSONFilePath.invalid) 33 | } 34 | } 35 | 36 | func testLoadingJSONFileNotAJSONDictionary() { 37 | expectError(.fileNotAJSONDictionary) { 38 | try JSONDictionary.from(url: JSONFilePath.rootArray) 39 | } 40 | } 41 | 42 | // MARK: Helpers 43 | 44 | fileprivate func expectError(_ expectedError: JSONUtilsError, file: StaticString = #file, line: UInt = #line, block: NoEscapeFunction ) { 45 | do { 46 | try block() 47 | } catch let error { 48 | XCTAssert(error is JSONUtilsError, file: file, line: line) 49 | if let jsonUtilsError = error as? JSONUtilsError { 50 | XCTAssertEqual(jsonUtilsError, expectedError, file: file, line: line) 51 | } 52 | return 53 | } 54 | XCTFail("No error thrown, expected: \(expectedError)", file: file, line: line) 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /Tests/JSONUtilitiesTests/Helpers/Dictionary+Equatable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Dictionary+Equatable.swift 3 | // JSONUtilities 4 | // 5 | // Created by Luciano Marisi on 08/05/2016. 6 | // Copyright © 2016 Luciano Marisi All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public func == (lhs: [String: Any], rhs: [String: Any] ) -> Bool { 12 | return NSDictionary(dictionary: lhs).isEqual(to: rhs) 13 | } 14 | 15 | public func == (lhs: [String: Any]?, rhs: [String: Any]? ) -> Bool { 16 | guard let lhs = lhs, let rhs = rhs else { return false } 17 | return NSDictionary(dictionary: lhs).isEqual(to: rhs) 18 | } 19 | 20 | public func == (lhs: [[String: Any]], rhs: [[String: Any]] ) -> Bool { 21 | let lhsArray = NSArray(array: lhs) 22 | let rhsArray = NSArray(array: rhs) 23 | return lhsArray.isEqual(to: rhsArray as [AnyObject]) 24 | } 25 | 26 | public func == (lhs: [[String: Any]]?, rhs: [[String: Any]]? ) -> Bool { 27 | guard let lhs = lhs, let rhs = rhs else { return false } 28 | let lhsArray = NSArray(array: lhs) 29 | let rhsArray = NSArray(array: rhs) 30 | return lhsArray.isEqual(to: rhsArray as [AnyObject]) 31 | } 32 | -------------------------------------------------------------------------------- /Tests/JSONUtilitiesTests/Helpers/XCTestCase+Additions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XCTestCase+Additions.swift 3 | // JSONUtilities 4 | // 5 | // Created by Luciano Marisi on 15/05/2016. 6 | // Copyright © 2016 Luciano Marisi All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import JSONUtilities 11 | 12 | extension XCTestCase { 13 | 14 | var testBundle: Bundle { 15 | return Bundle(for: type(of: self)) 16 | } 17 | 18 | func expectNoError(decode: () throws -> Void) { 19 | do { 20 | try decode() 21 | } catch { 22 | XCTFail("Should not throw error") 23 | } 24 | } 25 | 26 | func expectDecodingError(reason: JSONUtilities.DecodingError.Reason, keyPath: String, decode: () throws -> Void) { 27 | do { 28 | try decode() 29 | XCTFail("Decoding was supposed to throw \"\(reason)\" error") 30 | } catch { 31 | guard let error = error as? JSONUtilities.DecodingError else { 32 | XCTFail("Error is not a Decoding Error") 33 | return 34 | } 35 | XCTAssertTrue(error.reason == reason, "DecodingError failed because of \"\(error.reason)\" but was supposed to fail for \"\(reason)\"") 36 | XCTAssertTrue(error.keyPath == keyPath, "DecodingError failed at keyPath \"\(error.keyPath)\", but was supposed to fail at \"\(keyPath)\"") 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Tests/JSONUtilitiesTests/InlineDecodingTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InlineDecodingTests.swift 3 | // JSONUtilities 4 | // 5 | // Created by Luciano Marisi on 15/05/2016. 6 | // Copyright © 2016 Luciano Marisi All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import JSONUtilities 11 | 12 | private let randomKey = "aaaaaaa" 13 | 14 | class InlineDecodingTests: XCTestCase { 15 | 16 | func testDecodingOfJSONRawTypes() { 17 | let expectedInt: Int = 1 18 | expectDecodeType(expectedInt) 19 | 20 | let expectedFloat: Float = 2.2 21 | expectDecodeType(expectedFloat) 22 | 23 | let expectedDouble: Double = 2.2 24 | expectDecodeType(expectedDouble) 25 | 26 | let expectedString: String = "something" 27 | expectDecodeType(expectedString) 28 | 29 | let expectedBool: Bool = true 30 | expectDecodeType(expectedBool) 31 | } 32 | 33 | func testDecodingOfJSONDictionary() { 34 | 35 | let expectedValue: JSONDictionary = ["key1": "value1", "key2": "value2"] 36 | let dictionary = ["key": expectedValue] 37 | let decodedValue: JSONDictionary = try! dictionary.json(atKeyPath: "key") 38 | XCTAssert(decodedValue == expectedValue) 39 | 40 | let decodedOptionalInt: JSONDictionary? = dictionary.json(atKeyPath: "key") 41 | XCTAssert(decodedOptionalInt == expectedValue) 42 | 43 | expectDecodingError(reason: .keyNotFound, keyPath: randomKey) { 44 | let _ : JSONDictionary = try dictionary.json(atKeyPath: randomKey) 45 | } 46 | 47 | let decodedMissingInt: JSONDictionary? = dictionary.json(atKeyPath: randomKey) 48 | XCTAssertNil(decodedMissingInt) 49 | } 50 | 51 | func testDecodingOfJSONRawTypesArray() { 52 | let expectedInt: [Int] = [1] 53 | expectDecodeTypeArray(expectedInt) 54 | 55 | let expectedFloat: [Float] = [2.2] 56 | expectDecodeTypeArray(expectedFloat) 57 | 58 | let expectedDouble: [Double] = [2.2] 59 | expectDecodeTypeArray(expectedDouble) 60 | 61 | let expectedString: [String] = ["something"] 62 | expectDecodeTypeArray(expectedString) 63 | 64 | let expectedBool: [Bool] = [true] 65 | expectDecodeTypeArray(expectedBool) 66 | } 67 | 68 | func testIncorrectEnum() { 69 | 70 | let dictionary = ["enum": "three"] 71 | 72 | expectDecodingError(reason: .keyNotFound, keyPath: "enumIncorrect") { 73 | let _ : MockParent.MockEnum = try dictionary.json(atKeyPath: "enumIncorrect") 74 | } 75 | 76 | expectDecodingError(reason: .incorrectRawRepresentableRawValue, keyPath: "enum") { 77 | let _ : MockParent.MockEnum = try dictionary.json(atKeyPath: "enum") 78 | } 79 | } 80 | 81 | func test_decodingMandatoryEnumDictionary_withKey() { 82 | let dictionary: JSONDictionary = ["enums": ["value1": "one", "value2": "!@1", "value3": "two"]] 83 | 84 | let decodedEnums: [String: MockParent.MockEnum] = try! dictionary.json(atKeyPath: "enums") 85 | 86 | let expectedEnums: [String: MockParent.MockEnum] = ["value1": .one, "value3": .two] 87 | XCTAssertEqual(decodedEnums, expectedEnums) 88 | } 89 | 90 | func test_decodingOptionalEnumDictionary_withKey() { 91 | let dictionary: JSONDictionary = ["enums": ["value1": "one", "value2": "!@1", "value3": "two"]] 92 | 93 | let decodedEnums: [String: MockParent.MockEnum]? = dictionary.json(atKeyPath: "enums") 94 | 95 | let expectedEnums: [String: MockParent.MockEnum] = ["value1": .one, "value3": .two] 96 | XCTAssertEqual(decodedEnums!, expectedEnums) 97 | } 98 | 99 | func test_decodingMandatoryEnumArray_withKey() { 100 | let dictionary: JSONDictionary = ["enums": ["one", "!@1", "two"]] 101 | 102 | let decodedEnums: [MockParent.MockEnum] = try! dictionary.json(atKeyPath: "enums") 103 | 104 | let expectedEnums: [MockParent.MockEnum] = [.one, .two] 105 | XCTAssertEqual(decodedEnums, expectedEnums) 106 | } 107 | 108 | func test_decodingOptionalEnumArray_withKey() { 109 | let dictionary: JSONDictionary = ["enums": ["one", "!@1", "two"]] 110 | 111 | let decodedEnums: [MockParent.MockEnum]? = dictionary.json(atKeyPath: "enums") 112 | 113 | let expectedEnums: [MockParent.MockEnum] = [.one, .two] 114 | XCTAssertEqual(decodedEnums!, expectedEnums) 115 | } 116 | 117 | func test_decodingMandatoryEnumArray_withoutKey() { 118 | let dictionary: JSONDictionary = ["enums": ["one", "!@1", "two"]] 119 | 120 | expectDecodingError(reason: .keyNotFound, keyPath: "invalid_key") { 121 | let _ : MockParent.MockEnum = try dictionary.json(atKeyPath: "invalid_key") 122 | } 123 | } 124 | 125 | func test_decodingOptionalEnumArray_withoutKey() { 126 | let dictionary: JSONDictionary = ["enums": ["one", "!@1", "two"]] 127 | 128 | let decodedEnums: [MockParent.MockEnum]? = dictionary.json(atKeyPath: "invalid_key") 129 | 130 | XCTAssertNil(decodedEnums) 131 | } 132 | 133 | func testDecodingOfJSONDictionaryArray() { 134 | 135 | let expectedValue: [JSONDictionary] = [["key1": "value1"], ["key2": "value2"]] 136 | let dictionary = ["key": expectedValue] 137 | let decodedValue: [JSONDictionary] = try! dictionary.json(atKeyPath: "key") 138 | XCTAssert(decodedValue == expectedValue) 139 | 140 | let decodedOptionalInt: [JSONDictionary]? = dictionary.json(atKeyPath: "key") 141 | XCTAssert(decodedOptionalInt == expectedValue) 142 | 143 | expectDecodingError(reason: .keyNotFound, keyPath: randomKey) { 144 | let _ : [JSONDictionary] = try dictionary.json(atKeyPath: randomKey) 145 | } 146 | 147 | let decodedMissingInt: [JSONDictionary]? = dictionary.json(atKeyPath: randomKey) 148 | XCTAssertNil(decodedMissingInt) 149 | } 150 | 151 | func testSomeInvalidDecodableTypes() { 152 | let parentDictionary: JSONDictionary = ["children": ["john", ["name": "jane"]]] 153 | let decodedParent: MockSimpleParent = try! MockSimpleParent(jsonDictionary: parentDictionary) 154 | XCTAssert(decodedParent.children.count == 1) 155 | } 156 | 157 | // MARK: DecodingErrors 158 | 159 | func testDecodingErrorDescriptions() { 160 | let failureReasons: [JSONUtilities.DecodingError.Reason] = [ 161 | .keyNotFound, 162 | .incorrectRawRepresentableRawValue, 163 | .incorrectType, 164 | .conversionFailure 165 | ] 166 | failureReasons.forEach { 167 | let error = DecodingError(dictionary: [:], keyPath: "", expectedType: String.self, value: "", array: nil, reason: $0) 168 | XCTAssert(error.description.count > 0) 169 | XCTAssert(error.debugDescription.count > 0) 170 | XCTAssert(error.reason.description.count > 0) 171 | } 172 | } 173 | 174 | // MARK: Helpers 175 | 176 | fileprivate func expectDecodeType(_ expectedValue: ExpectedType, file: StaticString = #file, line: UInt = #line) { 177 | 178 | let dictionary = ["key": expectedValue] 179 | let decodedValue: ExpectedType = try! dictionary.json(atKeyPath: "key") 180 | XCTAssertEqual(decodedValue, expectedValue, file: file, line: line) 181 | 182 | let decodedOptionalInt: ExpectedType? = dictionary.json(atKeyPath: "key") 183 | XCTAssertEqual(decodedOptionalInt!, expectedValue, file: file, line: line) 184 | 185 | expectDecodingError(reason: .keyNotFound, keyPath: randomKey) { 186 | let _ : ExpectedType = try dictionary.json(atKeyPath: randomKey) 187 | } 188 | 189 | let decodedMissingInt: ExpectedType? = dictionary.json(atKeyPath: randomKey) 190 | XCTAssertNil(decodedMissingInt) 191 | } 192 | 193 | fileprivate func expectDecodeTypeArray(_ expectedValue: [ExpectedType], file: StaticString = #file, line: UInt = #line) { 194 | 195 | let dictionary = ["key": expectedValue] 196 | let decodedValue: [ExpectedType] = try! dictionary.json(atKeyPath: "key") 197 | XCTAssertEqual(decodedValue, expectedValue, file: file, line: line) 198 | 199 | let decodedOptionalInt: [ExpectedType]? = dictionary.json(atKeyPath: "key") 200 | XCTAssertEqual(decodedOptionalInt!, expectedValue, file: file, line: line) 201 | 202 | expectDecodingError(reason: .keyNotFound, keyPath: randomKey) { 203 | let _ : [ExpectedType] = try dictionary.json(atKeyPath: randomKey) 204 | } 205 | 206 | let decodedMissingInt: [ExpectedType]? = dictionary.json(atKeyPath: randomKey) 207 | XCTAssertNil(decodedMissingInt) 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /Tests/JSONUtilitiesTests/InvalidItemBehaviourTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InvalidItemBehaviourTests.swift 3 | // JSONUtilities 4 | // 5 | // Created by Yonas Kolb on 21/4/17. 6 | // Copyright © 2017 Luciano Marisi. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import JSONUtilities 11 | 12 | private let randomKey = "aaaaaaa" 13 | 14 | class InvalidItemBehaviourTests: XCTestCase { 15 | 16 | let key = "key" 17 | 18 | let dictionaryString = [ 19 | "key": [ 20 | "key1": "value1", 21 | "key2": 2 22 | ] 23 | ] 24 | 25 | let dictionaryConvertible = [ 26 | "key": [ 27 | "key1": "www.google.com", 28 | "key2": 2 29 | ] 30 | ] 31 | 32 | let dictionaryMockChild = [ 33 | "key": [ 34 | "key1": ["name": "john"], 35 | "key2": 2 36 | ] 37 | ] 38 | 39 | let arrayString = [ 40 | "key": [ 41 | "value1", 42 | 2 43 | ] 44 | ] 45 | 46 | let arrayConvertible = [ 47 | "key": [ 48 | "www.google.com", 49 | 2 50 | ] 51 | ] 52 | 53 | let arrayMockChild = [ 54 | "key": [ 55 | ["name": "john"], 56 | 2 57 | ] 58 | ] 59 | 60 | // MARK: Dictionary InvalidItemBehaviour.fail 61 | 62 | func test_stringJSONRawTypeDictionaryFails_whenThereAreInvalidObjects_and_invalidItemBehaviourIsFail() { 63 | expectDecodingError(reason: .incorrectType, keyPath: "key2") { 64 | let _ : [String: String] = try dictionaryString.json(atKeyPath: key, invalidItemBehaviour: .fail) 65 | } 66 | } 67 | 68 | func test_stringJSONPrimitiveConvertibleDictionaryFails_whenThereAreInvalidObjects_and_invalidItemBehaviourIsFail() { 69 | expectDecodingError(reason: .incorrectType, keyPath: "key2") { 70 | let _ : [String: URL] = try dictionaryConvertible.json(atKeyPath: key, invalidItemBehaviour: .fail) 71 | } 72 | } 73 | 74 | func test_stringJSONObjectConvertibleDictionaryFails_whenThereAreInvalidObjects_and_invalidItemBehaviourIsFail() { 75 | expectDecodingError(reason: .incorrectType, keyPath: "key2") { 76 | let _ : [String: MockSimpleChild] = try dictionaryMockChild.json(atKeyPath: key, invalidItemBehaviour: .fail) 77 | } 78 | } 79 | 80 | // MARK: Dictionary InvalidItemBehaviour.remove 81 | 82 | func test_stringJSONRawTypeDictionary_removesInvalidObjects_invalidItemBehaviourIsRemove() { 83 | expectNoError { 84 | let decodedDictionary: [String: String] = try dictionaryString.json(atKeyPath: key, invalidItemBehaviour: .remove) 85 | XCTAssert(decodedDictionary.count == 1) 86 | } 87 | } 88 | 89 | func test_stringJSONPrimitiveConvertibleDictionary_removesInvalidObjects_invalidItemBehaviourIsRemove() { 90 | expectNoError { 91 | let decodedDictionary: [String: URL] = try dictionaryConvertible.json(atKeyPath: key, invalidItemBehaviour: .remove) 92 | XCTAssert(decodedDictionary.count == 1) 93 | } 94 | } 95 | 96 | func test_stringJSONObjectConvertibleDictionary_removesInvalidObjects_invalidItemBehaviourIsRemove() { 97 | expectNoError { 98 | let decodedDictionary: [String: MockSimpleChild] = try dictionaryMockChild.json(atKeyPath: key, invalidItemBehaviour: .remove) 99 | XCTAssert(decodedDictionary.count == 1) 100 | } 101 | } 102 | 103 | // MARK: Array InvalidItemBehaviour.fail 104 | 105 | func test_stringJSONRawTypeArrayFails_whenThereAreInvalidObjects_and_invalidItemBehaviourIsFail() { 106 | expectDecodingError(reason: .incorrectType, keyPath: key) { 107 | let _ : [String: String] = try arrayString.json(atKeyPath: key, invalidItemBehaviour: .fail) 108 | } 109 | } 110 | 111 | func test_stringJSONPrimitiveConvertibleArrayFails_whenThereAreInvalidObjects_and_invalidItemBehaviourIsFail() { 112 | expectDecodingError(reason: .incorrectType, keyPath: key) { 113 | let _ : [URL] = try arrayConvertible.json(atKeyPath: key, invalidItemBehaviour: .fail) 114 | } 115 | } 116 | 117 | func test_stringJSONObjectConvertibleArrayFails_whenThereAreInvalidObjects_and_invalidItemBehaviourIsFail() { 118 | expectDecodingError(reason: .incorrectType, keyPath: key) { 119 | let _ : [MockSimpleChild] = try arrayMockChild.json(atKeyPath: key, invalidItemBehaviour: .fail) 120 | } 121 | } 122 | 123 | // MARK: Array InvalidItemBehaviour.remove 124 | 125 | func test_stringJSONRawTypeArray_removesInvalidObjects_invalidItemBehaviourIsRemove() { 126 | expectNoError { 127 | let decodedDictionary: [String] = try arrayString.json(atKeyPath: key, invalidItemBehaviour: .remove) 128 | XCTAssert(decodedDictionary.count == 1) 129 | } 130 | } 131 | 132 | func test_stringJSONPrimitiveConvertibleArray_removesInvalidObjects_invalidItemBehaviourIsRemove() { 133 | expectNoError { 134 | let decodedDictionary: [URL] = try arrayConvertible.json(atKeyPath: key, invalidItemBehaviour: .remove) 135 | XCTAssert(decodedDictionary.count == 1) 136 | } 137 | } 138 | 139 | func test_stringJSONObjectConvertibleArray_removesInvalidObjects_invalidItemBehaviourIsRemove() { 140 | expectNoError { 141 | let decodedDictionary: [MockSimpleChild] = try arrayMockChild.json(atKeyPath: key, invalidItemBehaviour: .remove) 142 | XCTAssert(decodedDictionary.count == 1) 143 | } 144 | } 145 | 146 | // MARK: Dictionary InvalidItemBehaviour.value 147 | 148 | func test_stringJSONRawTypeDictionary_setsValue_invalidItemBehaviourIsValue() { 149 | expectNoError { 150 | let decodedDictionary: [String: String] = try dictionaryString.json(atKeyPath: key, invalidItemBehaviour: .value("default")) 151 | XCTAssert(decodedDictionary["key2"] == "default") 152 | } 153 | } 154 | 155 | func test_stringJSONPrimitiveConvertibleDictionary_setsValue_invalidItemBehaviourIsValue() { 156 | expectNoError { 157 | let decodedDictionary: [String: URL] = try dictionaryConvertible.json(atKeyPath: key, invalidItemBehaviour: .value(URL(string: "test.com")!)) 158 | XCTAssert(decodedDictionary["key2"]?.absoluteString == "test.com") 159 | } 160 | } 161 | 162 | func test_stringJSONObjectConvertibleDictionaryy_setsValue_invalidItemBehaviourIsValue() { 163 | expectNoError { 164 | let decodedDictionary: [String: MockSimpleChild] = try dictionaryMockChild.json(atKeyPath: key, invalidItemBehaviour: .value(MockSimpleChild(name: "default"))) 165 | XCTAssert(decodedDictionary["key2"]?.name == "default") 166 | } 167 | } 168 | 169 | // MARK: Dictionary InvalidItemBehaviour.custom 170 | 171 | func test_stringJSONRawTypeDictionary_setsValue_invalidItemBehaviourIsCustom() { 172 | expectNoError { 173 | let decodedDictionary: [String: String] = try dictionaryString.json(atKeyPath: key, invalidItemBehaviour: .custom({.value("\($0.value)")})) 174 | XCTAssert(decodedDictionary["key2"] == "2") 175 | } 176 | } 177 | 178 | func test_stringJSONPrimitiveConvertibleDictionary_setsValue_invalidItemBehaviourIsCustom() { 179 | expectNoError { 180 | let decodedDictionary: [String: URL] = try dictionaryConvertible.json(atKeyPath: key, invalidItemBehaviour: .custom({.value(URL(string: "\($0.value)")!)})) 181 | XCTAssert(decodedDictionary["key2"]?.absoluteString == "2") 182 | } 183 | } 184 | 185 | func test_stringJSONObjectConvertibleDictionary_setsValue_invalidItemBehaviourIsCalculateValue() { 186 | expectNoError { 187 | let decodedDictionary: [String: MockSimpleChild] = try dictionaryMockChild.json(atKeyPath: key, invalidItemBehaviour: .custom({.value(MockSimpleChild(name: "\($0.value)"))})) 188 | XCTAssert(decodedDictionary["key2"]?.name == "2") 189 | } 190 | } 191 | 192 | // MARK: Array InvalidItemBehaviour.value 193 | 194 | func test_stringJSONRawTypeArray_setsValue_invalidItemBehaviourIsValue() { 195 | expectNoError { 196 | let decodedDictionary: [String] = try arrayString.json(atKeyPath: key, invalidItemBehaviour: .value("default")) 197 | XCTAssert(decodedDictionary.last == "default") 198 | } 199 | } 200 | 201 | func test_stringJSONPrimitiveConvertibleArray_setsValue_invalidItemBehaviourIsValue() { 202 | expectNoError { 203 | let decodedDictionary: [URL] = try arrayConvertible.json(atKeyPath: key, invalidItemBehaviour: .value(URL(string: "test.com")!)) 204 | XCTAssert(decodedDictionary.last?.absoluteString == "test.com") 205 | } 206 | } 207 | 208 | func test_stringJSONObjectConvertibleArray_setsValue_invalidItemBehaviourIsValue() { 209 | expectNoError { 210 | let decodedDictionary: [MockSimpleChild] = try arrayMockChild.json(atKeyPath: key, invalidItemBehaviour: .value(MockSimpleChild(name: "default"))) 211 | XCTAssert(decodedDictionary.last?.name == "default") 212 | } 213 | } 214 | 215 | // MARK: Array InvalidItemBehaviour.custom 216 | 217 | func test_stringJSONRawTypeArray_setsValue_invalidItemBehaviourIsCustom() { 218 | expectNoError { 219 | let decodedDictionary: [String] = try arrayString.json(atKeyPath: key, invalidItemBehaviour: .custom({ error in 220 | .value("\(error.value)") 221 | })) 222 | XCTAssert(decodedDictionary.last == "2") 223 | } 224 | } 225 | 226 | func test_stringJSONPrimitiveConvertibleArray_setsValue_invalidItemBehaviourIsCustom() { 227 | expectNoError { 228 | let decodedDictionary: [URL] = try arrayConvertible.json(atKeyPath: key, invalidItemBehaviour: .custom({ error in 229 | .value(URL(string: "\(error.value)")!) 230 | })) 231 | XCTAssert(decodedDictionary.last?.absoluteString == "2") 232 | } 233 | } 234 | 235 | func test_stringJSONObjectConvertibleArray_setsValue_invalidItemBehaviourIsCustom() { 236 | expectNoError { 237 | let decodedDictionary: [MockSimpleChild] = try arrayMockChild.json(atKeyPath: key, invalidItemBehaviour: .custom({ error in 238 | .value(MockSimpleChild(name: "\(error.value)")) 239 | })) 240 | XCTAssert(decodedDictionary.last?.name == "2") 241 | } 242 | } 243 | 244 | // MARK: InvalidItemBehaviour.custom 245 | 246 | func test_invalidItemBehaviourIsCustom_returnsValue() { 247 | expectNoError { 248 | let array: [String] = try arrayString.json(atKeyPath: key, invalidItemBehaviour: .custom({ _ in .value("2") })) 249 | XCTAssert(array.count == 2) 250 | } 251 | } 252 | 253 | func test_invalidItemBehaviourIsCustom_returnsRemove() { 254 | expectNoError { 255 | let array: [String] = try arrayString.json(atKeyPath: key, invalidItemBehaviour: .custom({ _ in .remove })) 256 | XCTAssert(array.count == 1) 257 | } 258 | } 259 | 260 | func test_invalidItemBehaviourIsCustom_returnsFail() { 261 | expectDecodingError(reason: .incorrectType, keyPath: key) { 262 | let _: [String] = try arrayString.json(atKeyPath: key, invalidItemBehaviour: .custom({ _ in .fail })) 263 | } 264 | } 265 | 266 | func test_invalidItemBehaviourIsCustom_returnsCustom() { 267 | expectDecodingError(reason: .incorrectType, keyPath: key) { 268 | let _: [String] = try arrayString.json(atKeyPath: key, invalidItemBehaviour: .custom({ _ in .custom({_ in .fail}) })) 269 | } 270 | } 271 | } 272 | -------------------------------------------------------------------------------- /Tests/JSONUtilitiesTests/JSONDecodingTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JSONDecodingTests.swift 3 | // JSONUtilities 4 | // 5 | // Created by Luciano Marisi on 21/11/2015. 6 | // Copyright © 2015 Luciano Marisi All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import JSONUtilities 11 | 12 | class JSONDecodingTests: XCTestCase { 13 | 14 | let expectedChild = MockChild(string: "stringValue", integer: 1, double: 1.2, bool: true) 15 | let expectedDictionary: JSONDictionary = ["doubleKey": 1.2, "integerKey": 1, "stringKey": "stringValue", "boolKey": true] 16 | let expectedDictionaryArray: [JSONDictionary] = [ 17 | ["doubleKey": 1.2, "integerKey": 1, "stringKey": "stringValue", "boolKey": true], 18 | ["doubleKey": 1.2, "integerKey": 1, "stringKey": "stringValue", "boolKey": true], 19 | ["randomTypeObject": 123] 20 | ] 21 | 22 | func testCorrectDecodingForMandatoryJSONOnParentWithChild() { 23 | do { 24 | let jsonDictionary = try JSONDictionary.from(url: JSONFilePath.correct) 25 | let mockJSONParent = try MockParent(jsonDictionary: jsonDictionary) 26 | XCTAssertEqual(mockJSONParent.mandatoryString, "stringValue") 27 | XCTAssertEqual(mockJSONParent.mandatoryInt, 1) 28 | XCTAssertEqual(mockJSONParent.mandatoryDouble, 1.2) 29 | XCTAssertEqual(mockJSONParent.mandatoryBool, true) 30 | XCTAssertTrue(mockJSONParent.mandatoryWeakDictionaryKey == expectedDictionary) 31 | XCTAssertEqual(mockJSONParent.mandatoryCustomJSONObject, expectedChild) 32 | XCTAssertEqual(mockJSONParent.mandatoryEnum, MockParent.MockEnum.one) 33 | 34 | XCTAssertEqual(mockJSONParent.optionalExistingString, "stringValue") 35 | XCTAssertEqual(mockJSONParent.optionalExistingInt, 1) 36 | XCTAssertEqual(mockJSONParent.optionalExistingDouble, 1.2) 37 | XCTAssertEqual(mockJSONParent.optionalExistingBool, true) 38 | XCTAssertTrue(mockJSONParent.optionalExistingWeakDictionaryKey == expectedDictionary) 39 | XCTAssertEqual(mockJSONParent.optionalExistingCustomJSONObject, expectedChild) 40 | XCTAssertEqual(mockJSONParent.optionalExistingEnum, MockParent.MockEnum.one) 41 | 42 | XCTAssertNil(mockJSONParent.optionalMissingString) 43 | XCTAssertNil(mockJSONParent.optionalMissingInt) 44 | XCTAssertNil(mockJSONParent.optionalMissingDouble) 45 | XCTAssertNil(mockJSONParent.optionalMissingBool) 46 | XCTAssertNil(mockJSONParent.optionalMissingWeakDictionaryKey) 47 | XCTAssertNil(mockJSONParent.optionalMissingCustomJSONObject) 48 | XCTAssertNil(mockJSONParent.optionalMissingEnum) 49 | 50 | XCTAssertEqual(mockJSONParent.mandatoryArrayString, ["1", "2"]) 51 | XCTAssertEqual(mockJSONParent.mandatoryArrayInt, [1, 2]) 52 | XCTAssertEqual(mockJSONParent.mandatoryArrayDouble, [1.1, 1.2]) 53 | XCTAssertEqual(mockJSONParent.mandatoryArrayBool, [true, false]) 54 | XCTAssertTrue(mockJSONParent.mandatoryWeakDictionaryArrayKey == expectedDictionaryArray) 55 | XCTAssertEqual(mockJSONParent.mandatoryArrayCustomJSONObject, [expectedChild, expectedChild]) 56 | 57 | XCTAssertEqual(mockJSONParent.optionalExistingArrayString!, ["1", "2"]) 58 | XCTAssertEqual(mockJSONParent.optionalExistingArrayInt!, [1, 2]) 59 | XCTAssertEqual(mockJSONParent.optionalExistingArrayDouble!, [1.1, 1.2]) 60 | XCTAssertEqual(mockJSONParent.optionalExistingArrayBool!, [true, false]) 61 | XCTAssertTrue(mockJSONParent.optionalExistingWeakDictionaryArrayKey == expectedDictionaryArray) 62 | XCTAssertEqual(mockJSONParent.optionalExistingArrayCustomJSONObject!, [expectedChild, expectedChild]) 63 | 64 | XCTAssertNil(mockJSONParent.optionalMissingArrayString) 65 | XCTAssertNil(mockJSONParent.optionalMissingArrayInt) 66 | XCTAssertNil(mockJSONParent.optionalMissingArrayDouble) 67 | XCTAssertNil(mockJSONParent.optionalMissingArrayBool) 68 | XCTAssertNil(mockJSONParent.optionalMissingWeakDictionaryArrayKey) 69 | XCTAssertNil(mockJSONParent.optionalMissingArrayCustomJSONObject) 70 | 71 | XCTAssertEqual(mockJSONParent.mandatoryIntDictionary, ["value1": 1, "value2": 2]) 72 | XCTAssertEqual(mockJSONParent.mandatoryObjectDictionary, ["value1": expectedChild, "value2": expectedChild]) 73 | XCTAssertEqual(mockJSONParent.mandatoryURLDictionary, ["value1": URL(string: "https://google.com")!, "value2": URL(string: "https://apple.com")!]) 74 | XCTAssertEqual(mockJSONParent.optionalIntDictionary!, ["value1": 1, "value2": 2]) 75 | XCTAssertEqual(mockJSONParent.optionalObjectDictionary!, ["value1": expectedChild, "value2": expectedChild]) 76 | XCTAssertEqual(mockJSONParent.optionalURLDictionary!, ["value1": URL(string: "https://google.com")!, "value2": URL(string: "https://apple.com")!]) 77 | 78 | } catch { 79 | XCTFail("Unexpected error: \(error)") 80 | } 81 | } 82 | 83 | func testIncorrectDecodingForMandatoryJSONRawType() { 84 | expectDecodingError(reason: .keyNotFound, keyPath: "keypath.mandatoryStringKey") { 85 | let jsonDictionary = try JSONDictionary.from(url: JSONFilePath.empty) 86 | let _ = try MockParent(jsonDictionary: jsonDictionary) 87 | } 88 | } 89 | 90 | func testIncorrectDecodingForMandatoryJSONRawTypeArray() { 91 | expectDecodingError(reason: .keyNotFound, keyPath: "keypath.mandatoryArrayStringKey") { 92 | let jsonDictionary = try JSONDictionary.from(url: JSONFilePath.correctWithoutRawArray) 93 | let _ = try MockParent(jsonDictionary: jsonDictionary) 94 | } 95 | } 96 | 97 | func testIncorrectDecodingForMandatoryJSONNestedObject() { 98 | expectDecodingError(reason: .keyNotFound, keyPath: "keypath.mandatoryCustomJSONObjectKey") { 99 | let jsonDictionary = try JSONDictionary.from(url: JSONFilePath.correctWithoutNested) 100 | let _ = try MockParent(jsonDictionary: jsonDictionary) 101 | } 102 | } 103 | 104 | func testIncorrectDecodingForMandatoryJSONNestedObjectArray() { 105 | expectDecodingError(reason: .keyNotFound, keyPath: "keypath.mandatoryArrayCustomJSONObjectKey") { 106 | let jsonDictionary = try JSONDictionary.from(url: JSONFilePath.correctWithoutNestedArray) 107 | let _ = try MockParent(jsonDictionary: jsonDictionary) 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /Tests/JSONUtilitiesTests/JSONPrimitiveConvertibleTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JSONPrimitiveConvertibleTests.swift 3 | // JSONUtilities 4 | // 5 | // Created by Luciano Marisi on 13/03/2016. 6 | // Copyright © 2016 Luciano Marisi All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import JSONUtilities 11 | 12 | private let invalidKey = "invalidKey" 13 | 14 | class JSONPrimitiveConvertibleTests: XCTestCase { 15 | 16 | func testDecodedAndTransformNSURL() { 17 | let urlString = "www.google.com" 18 | let expectedURL = URL(string: urlString) 19 | let jsonDictionary = [ 20 | "url": urlString, 21 | "invalid_url": "±" 22 | ] 23 | guard let mandatoryTransformedURL: URL = jsonDictionary.json(atKeyPath: "url") else { 24 | XCTFail(#function) 25 | return 26 | } 27 | XCTAssertEqual(expectedURL, mandatoryTransformedURL) 28 | 29 | let optionalTransformedURL: URL? = jsonDictionary.json(atKeyPath: "url") 30 | XCTAssertEqual(expectedURL, optionalTransformedURL) 31 | 32 | expectDecodingError(reason: .conversionFailure, keyPath: "invalid_url") { 33 | let _ : URL = try jsonDictionary.json(atKeyPath: "invalid_url") 34 | } 35 | } 36 | 37 | func testDecodedAndTransformNSURL_missingKey() { 38 | let jsonDictionary = ["url": "url"] 39 | 40 | expectDecodingError(reason: .keyNotFound, keyPath: invalidKey) { 41 | let _ : URL = try jsonDictionary.json(atKeyPath: invalidKey) 42 | } 43 | 44 | let urlFromMissingKey: URL? = jsonDictionary.json(atKeyPath: invalidKey) 45 | XCTAssertNil(urlFromMissingKey) 46 | } 47 | 48 | func testJSONPrimitiveConvertibleArray() { 49 | let expectedURLStrings = ["www.google.com", "www.apple.com"] 50 | let expectedURLs = expectedURLStrings.compactMap { URL(string: $0) } 51 | let jsonDictionary = ["urls": expectedURLStrings] 52 | let decodedURLs: [URL] = try! jsonDictionary.json(atKeyPath: "urls") 53 | XCTAssertEqual(decodedURLs, expectedURLs) 54 | 55 | expectDecodingError(reason: .keyNotFound, keyPath: invalidKey) { 56 | let _ : URL = try jsonDictionary.json(atKeyPath: invalidKey) 57 | } 58 | 59 | let decodedOptionalURLs: [URL]? = jsonDictionary.json(atKeyPath: "urls") 60 | XCTAssertEqual(decodedOptionalURLs!, expectedURLs) 61 | 62 | let decodedMissingURLs: [URL]? = jsonDictionary.json(atKeyPath: invalidKey) 63 | XCTAssertNil(decodedMissingURLs) 64 | } 65 | 66 | func testJSONPrimitiveConvertibleArray_failsOnNonTransformable() { 67 | let expectedURLStrings = ["www.google.com", "±"] 68 | let jsonDictionary = ["urls": expectedURLStrings] 69 | 70 | expectDecodingError(reason: .conversionFailure, keyPath: "urls") { 71 | let _ : [URL] = try jsonDictionary.json(atKeyPath: "urls", invalidItemBehaviour: .fail) 72 | } 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /Tests/JSONUtilitiesTests/Metadata/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Tests/JSONUtilitiesTests/Mocks/MockChild.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockChild.swift 3 | // JSONUtilities 4 | // 5 | // Created by Luciano Marisi on 15/05/2016. 6 | // Copyright © 2016 Luciano Marisi All rights reserved. 7 | // 8 | 9 | import Foundation 10 | @testable import JSONUtilities 11 | 12 | struct MockChild { 13 | let string: String 14 | let integer: Int 15 | let double: Double 16 | let bool: Bool 17 | } 18 | 19 | extension MockChild : JSONObjectConvertible { 20 | init(jsonDictionary: JSONDictionary) throws { 21 | string = try jsonDictionary.json(atKeyPath: "stringKey") 22 | integer = try jsonDictionary.json(atKeyPath: "integerKey") 23 | double = try jsonDictionary.json(atKeyPath: "doubleKey") 24 | bool = try jsonDictionary.json(atKeyPath: "boolKey") 25 | } 26 | } 27 | 28 | // MARK: Extensions necessary for testing 29 | 30 | extension MockChild : Equatable {} 31 | 32 | func == (lhs: MockChild, rhs: MockChild) -> Bool { 33 | return lhs.string == rhs.string && 34 | lhs.integer == rhs.integer && 35 | lhs.double == rhs.double && 36 | lhs.bool == rhs.bool 37 | } 38 | -------------------------------------------------------------------------------- /Tests/JSONUtilitiesTests/Mocks/MockParent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockParent.swift 3 | // JSONUtilities 4 | // 5 | // Created by Luciano Marisi on 15/05/2016. 6 | // Copyright © 2016 Luciano Marisi All rights reserved. 7 | // 8 | 9 | import Foundation 10 | @testable import JSONUtilities 11 | 12 | private let randomKey = "asdfghj" 13 | 14 | struct MockParent { 15 | 16 | enum MockEnum: String { 17 | case one 18 | case two 19 | } 20 | 21 | // MARK: JSON raw types and custom objects properties 22 | let mandatoryString: String 23 | let mandatoryInt: Int 24 | let mandatoryDouble: Double 25 | let mandatoryBool: Bool 26 | let mandatoryWeakDictionaryKey: JSONDictionary 27 | let mandatoryCustomJSONObject: MockChild 28 | let mandatoryEnum: MockEnum 29 | 30 | let optionalExistingString: String? 31 | let optionalExistingInt: Int? 32 | let optionalExistingDouble: Double? 33 | let optionalExistingBool: Bool? 34 | let optionalExistingWeakDictionaryKey: JSONDictionary? 35 | let optionalExistingCustomJSONObject: MockChild? 36 | let optionalExistingEnum: MockEnum? 37 | 38 | let optionalMissingString: String? 39 | let optionalMissingInt: Int? 40 | let optionalMissingDouble: Double? 41 | let optionalMissingBool: Bool? 42 | let optionalMissingWeakDictionaryKey: JSONDictionary? 43 | let optionalMissingCustomJSONObject: MockChild? 44 | let optionalMissingEnum: MockEnum? 45 | 46 | // MARK: Array properties 47 | let mandatoryArrayString: [String] 48 | let mandatoryArrayInt: [Int] 49 | let mandatoryArrayDouble: [Double] 50 | let mandatoryArrayBool: [Bool] 51 | let mandatoryWeakDictionaryArrayKey: [JSONDictionary] 52 | let mandatoryArrayCustomJSONObject: [MockChild] 53 | 54 | let optionalExistingArrayString: [String]? 55 | let optionalExistingArrayInt: [Int]? 56 | let optionalExistingArrayDouble: [Double]? 57 | let optionalExistingArrayBool: [Bool]? 58 | let optionalExistingWeakDictionaryArrayKey: [JSONDictionary]? 59 | let optionalExistingArrayCustomJSONObject: [MockChild]? 60 | 61 | let optionalMissingArrayString: [String]? 62 | let optionalMissingArrayInt: [Int]? 63 | let optionalMissingArrayDouble: [Double]? 64 | let optionalMissingArrayBool: [Bool]? 65 | let optionalMissingWeakDictionaryArrayKey: [JSONDictionary]? 66 | let optionalMissingArrayCustomJSONObject: [MockChild]? 67 | 68 | let mandatoryIntDictionary: [String: Int] 69 | let mandatoryObjectDictionary: [String: MockChild] 70 | let mandatoryURLDictionary: [String: URL] 71 | let optionalIntDictionary: [String: Int]? 72 | let optionalObjectDictionary: [String: MockChild]? 73 | let optionalURLDictionary: [String: URL]? 74 | 75 | init(jsonDictionary: JSONDictionary) throws { 76 | mandatoryString = try jsonDictionary.json(atKeyPath: "keypath.mandatoryStringKey") 77 | mandatoryInt = try jsonDictionary.json(atKeyPath: "keypath.mandatoryIntKey") 78 | mandatoryDouble = try jsonDictionary.json(atKeyPath: "keypath.mandatoryDoubleKey") 79 | mandatoryBool = try jsonDictionary.json(atKeyPath: "keypath.mandatoryBoolKey") 80 | mandatoryWeakDictionaryKey = try jsonDictionary.json(atKeyPath: "keypath.mandatoryCustomJSONObjectKey") 81 | mandatoryCustomJSONObject = try jsonDictionary.json(atKeyPath: "keypath.mandatoryCustomJSONObjectKey") 82 | mandatoryEnum = try jsonDictionary.json(atKeyPath: "keypath.mandatoryEnum") 83 | 84 | optionalExistingString = jsonDictionary.json(atKeyPath: "keypath.mandatoryStringKey") 85 | optionalExistingInt = jsonDictionary.json(atKeyPath: "keypath.mandatoryIntKey") 86 | optionalExistingDouble = jsonDictionary.json(atKeyPath: "keypath.mandatoryDoubleKey") 87 | optionalExistingBool = jsonDictionary.json(atKeyPath: "keypath.mandatoryBoolKey") 88 | optionalExistingWeakDictionaryKey = jsonDictionary.json(atKeyPath: "keypath.mandatoryCustomJSONObjectKey") 89 | optionalExistingCustomJSONObject = jsonDictionary.json(atKeyPath: "keypath.mandatoryCustomJSONObjectKey") 90 | optionalExistingEnum = jsonDictionary.json(atKeyPath: "keypath.mandatoryEnum") 91 | 92 | optionalMissingString = jsonDictionary.json(atKeyPath: randomKey) 93 | optionalMissingInt = jsonDictionary.json(atKeyPath: randomKey) 94 | optionalMissingDouble = jsonDictionary.json(atKeyPath: randomKey) 95 | optionalMissingBool = jsonDictionary.json(atKeyPath: randomKey) 96 | optionalMissingWeakDictionaryKey = jsonDictionary.json(atKeyPath: randomKey) 97 | optionalMissingCustomJSONObject = jsonDictionary.json(atKeyPath: randomKey) 98 | optionalMissingEnum = jsonDictionary.json(atKeyPath: randomKey) 99 | 100 | mandatoryArrayString = try jsonDictionary.json(atKeyPath: "keypath.mandatoryArrayStringKey") 101 | mandatoryArrayInt = try jsonDictionary.json(atKeyPath: "keypath.mandatoryArrayIntKey") 102 | mandatoryArrayDouble = try jsonDictionary.json(atKeyPath: "keypath.mandatoryArrayDoubleKey") 103 | mandatoryArrayBool = try jsonDictionary.json(atKeyPath: "keypath.mandatoryArrayBoolKey") 104 | mandatoryWeakDictionaryArrayKey = try jsonDictionary.json(atKeyPath: "keypath.mandatoryArrayCustomJSONObjectKey") 105 | mandatoryArrayCustomJSONObject = try jsonDictionary.json(atKeyPath: "keypath.mandatoryArrayCustomJSONObjectKey") 106 | 107 | optionalExistingArrayString = jsonDictionary.json(atKeyPath: "keypath.mandatoryArrayStringKey") 108 | optionalExistingArrayInt = jsonDictionary.json(atKeyPath: "keypath.mandatoryArrayIntKey") 109 | optionalExistingArrayDouble = jsonDictionary.json(atKeyPath: "keypath.mandatoryArrayDoubleKey") 110 | optionalExistingArrayBool = jsonDictionary.json(atKeyPath: "keypath.mandatoryArrayBoolKey") 111 | optionalExistingWeakDictionaryArrayKey = jsonDictionary.json(atKeyPath: "keypath.mandatoryArrayCustomJSONObjectKey") 112 | optionalExistingArrayCustomJSONObject = jsonDictionary.json(atKeyPath: "keypath.mandatoryArrayCustomJSONObjectKey") 113 | 114 | optionalMissingArrayString = jsonDictionary.json(atKeyPath: randomKey) 115 | optionalMissingArrayInt = jsonDictionary.json(atKeyPath: randomKey) 116 | optionalMissingArrayDouble = jsonDictionary.json(atKeyPath: randomKey) 117 | optionalMissingArrayBool = jsonDictionary.json(atKeyPath: randomKey) 118 | optionalMissingWeakDictionaryArrayKey = jsonDictionary.json(atKeyPath: randomKey) 119 | optionalMissingArrayCustomJSONObject = jsonDictionary.json(atKeyPath: randomKey) 120 | 121 | mandatoryIntDictionary = try jsonDictionary.json(atKeyPath: "keypath.mandatoryIntDictionary") 122 | mandatoryObjectDictionary = try jsonDictionary.json(atKeyPath: "keypath.mandatoryObjectDictionary") 123 | mandatoryURLDictionary = try jsonDictionary.json(atKeyPath: "keypath.mandatoryURLDictionary") 124 | 125 | optionalIntDictionary = jsonDictionary.json(atKeyPath: "keypath.mandatoryIntDictionary") 126 | optionalObjectDictionary = jsonDictionary.json(atKeyPath: "keypath.mandatoryObjectDictionary") 127 | optionalURLDictionary = jsonDictionary.json(atKeyPath: "keypath.mandatoryURLDictionary") 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /Tests/JSONUtilitiesTests/Mocks/MockSimpleChild.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockSimpleChild.swift 3 | // JSONUtilities 4 | // 5 | // Created by Luciano Marisi on 15/05/2016. 6 | // Copyright © 2016 Luciano Marisi All rights reserved. 7 | // 8 | 9 | import Foundation 10 | @testable import JSONUtilities 11 | 12 | struct MockSimpleChild { 13 | let name: String 14 | } 15 | 16 | extension MockSimpleChild: JSONObjectConvertible { 17 | 18 | init(jsonDictionary: JSONDictionary) throws { 19 | name = try jsonDictionary.json(atKeyPath: "name") 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /Tests/JSONUtilitiesTests/Mocks/MockSimpleParent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockSimpleParent.swift 3 | // JSONUtilities 4 | // 5 | // Created by Luciano Marisi on 15/05/2016. 6 | // Copyright © 2016 Luciano Marisi All rights reserved. 7 | // 8 | 9 | import Foundation 10 | @testable import JSONUtilities 11 | 12 | struct MockSimpleParent { 13 | let children: [MockSimpleChild] 14 | } 15 | 16 | extension MockSimpleParent: JSONObjectConvertible { 17 | 18 | init(jsonDictionary: JSONDictionary) throws { 19 | children = try jsonDictionary.json(atKeyPath: "children") 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /Tests/JSONUtilitiesTests/TestFiles/JSONFiles.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JSONFiles.swift 3 | // JSONUtilities 4 | // 5 | // Created by Luciano Marisi on 21/11/2015. 6 | // Copyright © 2015 Luciano Marisi All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct JSONFilename { 12 | static let correct = "correct" 13 | static let missing = "missing" 14 | } 15 | 16 | struct JSONFilePath { 17 | static let correct = "correct".filePath 18 | static let empty = "empty".filePath 19 | static let correctWithoutNested = "correct_without_nested_object".filePath 20 | static let correctWithoutNestedArray = "correct_with_missing_nested_array".filePath 21 | static let correctWithoutRawArray = "correct_with_missing_raw_array".filePath 22 | static let missing = "missing".filePath 23 | static let invalid = "invalid".filePath 24 | static let rootArray = "root_array".filePath 25 | } 26 | 27 | private extension String { 28 | var filePath: URL { 29 | let parentPath = (#file).components(separatedBy: "/").dropLast().joined(separator: "/") 30 | return URL(string: "file://\(parentPath)/\(self).json")! 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Tests/JSONUtilitiesTests/TestFiles/correct.json: -------------------------------------------------------------------------------- 1 | { 2 | "keypath": { 3 | "mandatoryStringKey": "stringValue", 4 | "mandatoryIntKey": 1, 5 | "mandatoryDoubleKey": 1.2, 6 | "mandatoryBoolKey": true, 7 | "mandatoryCustomJSONObjectKey": { 8 | "stringKey": "stringValue", 9 | "integerKey": 1, 10 | "doubleKey": 1.2, 11 | "boolKey": true 12 | }, 13 | 14 | "mandatoryArrayStringKey": ["1", "2"], 15 | "mandatoryArrayIntKey": [1, 2], 16 | "mandatoryArrayDoubleKey": [1.1, 1.2], 17 | "mandatoryArrayBoolKey": [true, false], 18 | "mandatoryArrayCustomJSONObjectKey": [{ 19 | "stringKey": "stringValue", 20 | "integerKey": 1, 21 | "doubleKey": 1.2, 22 | "boolKey": true 23 | }, 24 | { 25 | "stringKey": "stringValue", 26 | "integerKey": 1, 27 | "doubleKey": 1.2, 28 | "boolKey": true 29 | }, 30 | { 31 | "randomTypeObject": 123 32 | } 33 | ], 34 | "mandatoryWeakDictionaryKey": { 35 | "stringKey": "value", 36 | "boolKey": true 37 | }, 38 | "mandatoryEnum": "one", 39 | "mandatoryIntDictionary": { 40 | "value1": 1, 41 | "value2": 2, 42 | "invalid": "some invalid" 43 | }, 44 | "mandatoryObjectDictionary": { 45 | "value1": { 46 | "stringKey": "stringValue", 47 | "integerKey": 1, 48 | "doubleKey": 1.2, 49 | "boolKey": true 50 | }, 51 | "value2": { 52 | "stringKey": "stringValue", 53 | "integerKey": 1, 54 | "doubleKey": 1.2, 55 | "boolKey": true 56 | }, 57 | "invalid": { 58 | "stringKey": "stringValue" 59 | } 60 | }, 61 | "mandatoryURLDictionary": { 62 | "value1": "https://google.com", 63 | "value2": "https://apple.com", 64 | "invalid": 2 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Tests/JSONUtilitiesTests/TestFiles/correct_with_missing_nested_array.json: -------------------------------------------------------------------------------- 1 | { 2 | "keypath": { 3 | "mandatoryStringKey": "stringValue", 4 | "mandatoryIntKey": 1, 5 | "mandatoryDoubleKey": 1.2, 6 | "mandatoryBoolKey": true, 7 | "mandatoryCustomJSONObjectKey": { 8 | "stringKey": "stringValue", 9 | "integerKey": 1, 10 | "doubleKey": 1.2, 11 | "boolKey": true 12 | }, 13 | "mandatoryArrayStringKey": ["1", "2"], 14 | "mandatoryArrayIntKey": [1, 2], 15 | "mandatoryArrayDoubleKey": [1.1, 1.2], 16 | "mandatoryArrayBoolKey": [true, false], 17 | "mandatoryEnum": "one" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Tests/JSONUtilitiesTests/TestFiles/correct_with_missing_raw_array.json: -------------------------------------------------------------------------------- 1 | { 2 | "keypath": { 3 | "mandatoryStringKey": "stringValue", 4 | "mandatoryIntKey": 1, 5 | "mandatoryDoubleKey": 1.2, 6 | "mandatoryBoolKey": true, 7 | "mandatoryCustomJSONObjectKey": { 8 | "stringKey": "stringValue", 9 | "integerKey": 1, 10 | "doubleKey": 1.2, 11 | "boolKey": true 12 | }, 13 | "mandatoryEnum": "one" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Tests/JSONUtilitiesTests/TestFiles/correct_without_nested_object.json: -------------------------------------------------------------------------------- 1 | { 2 | "keypath": { 3 | "mandatoryStringKey": "stringValue", 4 | "mandatoryIntKey": 1, 5 | "mandatoryDoubleKey": 1.2, 6 | "mandatoryBoolKey": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Tests/JSONUtilitiesTests/TestFiles/empty.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /Tests/JSONUtilitiesTests/TestFiles/invalid.json: -------------------------------------------------------------------------------- 1 | x -------------------------------------------------------------------------------- /Tests/JSONUtilitiesTests/TestFiles/root_array.json: -------------------------------------------------------------------------------- 1 | ["asd"] -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | comment: 2 | layout: header, changes, diff 3 | coverage: 4 | ignore: 5 | - Sources/JSONUtilities/Deprecations.swift 6 | - Tests/.* 7 | - NonSwiftPackageManagerTests/.* 8 | status: 9 | patch: false 10 | --------------------------------------------------------------------------------