diff --git a/Marshal.xcodeproj/project.pbxproj b/Marshal.xcodeproj/project.pbxproj index 9f40e7b..a3e62b1 100644 --- a/Marshal.xcodeproj/project.pbxproj +++ b/Marshal.xcodeproj/project.pbxproj @@ -15,6 +15,7 @@ 71EFC3A81CCB4EAF00394E57 /* PerformanceTestObjects.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71EFC3A71CCB4EAF00394E57 /* PerformanceTestObjects.swift */; }; 71EFC3AA1CCB505C00394E57 /* Large.json in Resources */ = {isa = PBXBuildFile; fileRef = 71EFC3A91CCB505C00394E57 /* Large.json */; }; 71EFC3AC1CCB513300394E57 /* PerformanceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71EFC3AB1CCB513300394E57 /* PerformanceTests.swift */; }; + ABEFEBDB1DF4BFEA00D6D6CD /* PeopleByKey.json in Resources */ = {isa = PBXBuildFile; fileRef = ABEFEBDA1DF4BFEA00D6D6CD /* PeopleByKey.json */; }; AE5720E71C7FE97E00D45DE3 /* TestSimpleSet.json in Resources */ = {isa = PBXBuildFile; fileRef = AE5720E61C7FE97E00D45DE3 /* TestSimpleSet.json */; }; AECB67181CD317CA00141059 /* MarshalDictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = AECB67171CD317CA00141059 /* MarshalDictionary.swift */; }; AFBED2531C7E1AAB00622331 /* Marshal.h in Headers */ = {isa = PBXBuildFile; fileRef = AFBED2521C7E1AAB00622331 /* Marshal.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -53,6 +54,7 @@ 71EFC3A71CCB4EAF00394E57 /* PerformanceTestObjects.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PerformanceTestObjects.swift; sourceTree = ""; }; 71EFC3A91CCB505C00394E57 /* Large.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = Large.json; sourceTree = ""; }; 71EFC3AB1CCB513300394E57 /* PerformanceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PerformanceTests.swift; sourceTree = ""; }; + ABEFEBDA1DF4BFEA00D6D6CD /* PeopleByKey.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = PeopleByKey.json; sourceTree = ""; }; AE5720E61C7FE97E00D45DE3 /* TestSimpleSet.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = TestSimpleSet.json; sourceTree = ""; }; AECB67171CD317CA00141059 /* MarshalDictionary.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MarshalDictionary.swift; path = Sources/MarshalDictionary.swift; sourceTree = SOURCE_ROOT; }; AFBED24F1C7E1AAB00622331 /* Marshal.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Marshal.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -138,6 +140,7 @@ children = ( 71EFC3A91CCB505C00394E57 /* Large.json */, 7112BA971C7ECD5600B657F9 /* People.json */, + ABEFEBDA1DF4BFEA00D6D6CD /* PeopleByKey.json */, 7112BA981C7ECD5600B657F9 /* TestDictionary.json */, 7112BA991C7ECD5600B657F9 /* TestObjectArray.json */, 7112BA9A1C7ECD5600B657F9 /* TestSimpleArray.json */, @@ -253,6 +256,7 @@ buildActionMask = 2147483647; files = ( 7112BA9B1C7ECD5600B657F9 /* People.json in Resources */, + ABEFEBDB1DF4BFEA00D6D6CD /* PeopleByKey.json in Resources */, AE5720E71C7FE97E00D45DE3 /* TestSimpleSet.json in Resources */, 71EFC3AA1CCB505C00394E57 /* Large.json in Resources */, 7112BA9E1C7ECD5600B657F9 /* TestSimpleArray.json in Resources */, diff --git a/MarshalTests/MarshalTests.swift b/MarshalTests/MarshalTests.swift index a8a4391..26843d4 100644 --- a/MarshalTests/MarshalTests.swift +++ b/MarshalTests/MarshalTests.swift @@ -154,6 +154,10 @@ class MarshalTests: XCTestCase { person = people[1] let dead = try! !person.value(for: "living") XCTAssertTrue(dead) + + let result: [String: Bool] = try! json.value(for: "result") + XCTAssertEqual(result.count, 1) + XCTAssertEqual(result["ok"], true) } func testSimpleArray() { @@ -232,6 +236,17 @@ class MarshalTests: XCTestCase { waitForExpectations(timeout: 1, handler: nil) } + + func testContainedCustomObjects() { + let path = Bundle(for: type(of: self)).path(forResource: "PeopleByKey", ofType: "json")! + let data = try! Data(contentsOf: URL(fileURLWithPath: path)) + let obj = try! JSONParser.JSONObjectWithData(data) + + let people: [String: Person] = try! obj.value(for: "people") + XCTAssertNotNil(people["person_id1"]) + XCTAssertEqual(people.count, 2) + XCTAssertEqual(people["person_id1"]!.firstName, "Jason") + } enum MyEnum: String { case one = "One" diff --git a/MarshalTests/PeopleByKey.json b/MarshalTests/PeopleByKey.json new file mode 100644 index 0000000..b311b5c --- /dev/null +++ b/MarshalTests/PeopleByKey.json @@ -0,0 +1,6 @@ +{ + "people":{ + "person_id1": {"first":"Jason","last":"Larsen", "address":null, "score":42}, + "person_id2": {"first":"Bart","last":"Whiteley", "score":47} + }, +} diff --git a/MarshalTests/TestDictionary.json b/MarshalTests/TestDictionary.json index f28f9b3..cc0e9c1 100644 --- a/MarshalTests/TestDictionary.json +++ b/MarshalTests/TestDictionary.json @@ -6,5 +6,8 @@ "meta":{ "next":"http://apple.com", "prev":null + }, + "result": { + "ok": true } -} \ No newline at end of file +} diff --git a/Sources/JSON.swift b/Sources/JSON.swift index 2f3b365..e3ce1c3 100644 --- a/Sources/JSON.swift +++ b/Sources/JSON.swift @@ -26,8 +26,11 @@ public struct JSONParser { fileprivate init() { } public static func JSONObjectWithData(_ data: Data) throws -> JSONObject { - let obj: Any = try JSONSerialization.jsonObject(with: data, options: []) - return try JSONObject.value(from: obj) + let object: Any = try JSONSerialization.jsonObject(with: data, options: []) + guard let objectValue = object as? JSONObject else { + throw MarshalError.typeMismatch(expected: JSONObject.self, actual: type(of: object)) + } + return objectValue } public static func JSONArrayWithData(_ data: Data) throws -> [JSONObject] { diff --git a/Sources/MarshaledObject.swift b/Sources/MarshaledObject.swift index 59a5ad8..5be9316 100644 --- a/Sources/MarshaledObject.swift +++ b/Sources/MarshaledObject.swift @@ -107,7 +107,45 @@ public extension MarshaledObject { return nil } } - + + public func value(for key: KeyType) throws -> [String: A] { + let any = try self.any(for: key) + do { + return try [String: A].value(from: any) + } + catch let MarshalError.typeMismatch(expected: expected, actual: actual) { + throw MarshalError.typeMismatchWithKey(key: key.stringValue, expected: expected, actual: actual) + } + } + + public func value(for key: KeyType) throws -> [String: A]? { + do { + return try [String: A].value(from: any) + } + catch MarshalError.keyNotFound { + return nil + } + catch MarshalError.nullValue { + return nil + } + } + + public func value(for key: KeyType) throws -> [MarshalDictionary] { + let any = try self.any(for: key) + guard let object = any as? [MarshalDictionary] else { + throw MarshalError.typeMismatchWithKey(key: key.stringValue, expected: [MarshalDictionary].self, actual: type(of: any)) + } + return object + } + + public func value(for key: KeyType) throws -> MarshalDictionary { + let any = try self.any(for: key) + guard let object = any as? MarshalDictionary else { + throw MarshalError.typeMismatchWithKey(key: key.stringValue, expected: MarshalDictionary.self, actual: type(of: any)) + } + return object + } + public func value(for key: KeyType) throws -> Set { let any = try self.any(for: key) do { diff --git a/Sources/Operators.swift b/Sources/Operators.swift index afda9cd..f2a4b75 100644 --- a/Sources/Operators.swift +++ b/Sources/Operators.swift @@ -46,3 +46,9 @@ public func <| (dictionary: MarshaledObject, key: String) t public func <| (dictionary: MarshaledObject, key: String) throws -> [A]? where A.RawValue: ValueType { return try dictionary.value(for: key) } +public func <| (dictionary: MarshaledObject, key: String) throws -> JSONObject { + return try dictionary.value(for: key) +} +public func <| (dictionary: MarshaledObject, key: String) throws -> [JSONObject] { + return try dictionary.value(for: key) +} diff --git a/Sources/ValueType.swift b/Sources/ValueType.swift index d05420b..7bb5332 100644 --- a/Sources/ValueType.swift +++ b/Sources/ValueType.swift @@ -88,12 +88,19 @@ extension Array where Element: ValueType { } } -extension Dictionary: ValueType { - public static func value(from object: Any) throws -> [Key: Value] { - guard let objectValue = object as? [Key: Value] else { +extension Dictionary where Value: ValueType { + public static func value(from object: Any) throws -> Dictionary { + guard let objectValue = object as? [Key: Any] else { throw MarshalError.typeMismatch(expected: self, actual: type(of: object)) } - return objectValue + var result: [Key: Value] = [:] + for (k, v) in objectValue { + guard let value = try Value.value(from: v) as? Value else { + throw MarshalError.typeMismatch(expected: Value.self, actual: type(of: any)) + } + result[k] = value + } + return result } }