├── .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 | [](https://travis-ci.org/lucianomarisi/JSONUtilities)
4 | [](https://cocoapods.org/pods/JSONUtilities)
5 | [](https://cocoapods.org/pods/JSONUtilities)
6 | [](http://codecov.io/github/lucianomarisi/JSONUtilities?branch=master)
7 | 
8 | 
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 |
--------------------------------------------------------------------------------