From da64650718279984d0de1bcb56cb6024d2794ba2 Mon Sep 17 00:00:00 2001 From: Tristan Himmelman Date: Sat, 16 May 2015 23:37:41 -0400 Subject: [PATCH 1/2] - added support for dictionary of arrays of custom objects - added test for the above --- ObjectMapper/Core/FromJSON.swift | 19 +++ ObjectMapper/Core/Mapper.swift | 136 ++++++++++------------ ObjectMapper/Core/Operators.swift | 129 +++++++++----------- ObjectMapper/Core/ToJSON.swift | 14 +++ ObjectMapperTests/ObjectMapperTests.swift | 34 +++++- 5 files changed, 183 insertions(+), 149 deletions(-) diff --git a/ObjectMapper/Core/FromJSON.swift b/ObjectMapper/Core/FromJSON.swift index a8d82f38..f82af633 100755 --- a/ObjectMapper/Core/FromJSON.swift +++ b/ObjectMapper/Core/FromJSON.swift @@ -83,4 +83,23 @@ internal final class FromJSON { class func optionalObjectDictionary(inout field: Dictionary!, object: AnyObject?) { field = Mapper().mapDictionary(object) } + + /// Dictionary containing Array of Mappable objects + class func objectDictionaryOfArrays(inout field: Dictionary, object: AnyObject?) { + let parsedObjects = Mapper().mapDictionaryOfArrays(object) + + if let objects = parsedObjects { + field = objects + } + } + + /// Optional Dictionary containing Array of Mappable objects + class func optionalObjectDictionaryOfArrays(inout field: Dictionary?, object: AnyObject?) { + field = Mapper().mapDictionaryOfArrays(object) + } + + /// Implicitly unwrapped Dictionary containing Array of Mappable objects + class func optionalObjectDictionaryOfArrays(inout field: Dictionary!, object: AnyObject?) { + field = Mapper().mapDictionaryOfArrays(object) + } } diff --git a/ObjectMapper/Core/Mapper.swift b/ObjectMapper/Core/Mapper.swift index d86a23ff..b5ce6e76 100755 --- a/ObjectMapper/Core/Mapper.swift +++ b/ObjectMapper/Core/Mapper.swift @@ -18,9 +18,8 @@ public enum MappingType { case ToJSON } -/** -* A class used for holding mapping data -*/ + +/// A class used for holding mapping data public final class Map { public let mappingType: MappingType @@ -36,11 +35,9 @@ public final class Map { self.JSONDictionary = JSONDictionary } - /** - * Sets the current mapper value and key. - * - * The Key paramater can be a period separated string (ex. "distance.value") to access sub objects. - */ + + /// Sets the current mapper value and key. + /// The Key paramater can be a period separated string (ex. "distance.value") to access sub objects. public subscript(key: String) -> Map { // save key and value associated to it currentKey = key @@ -82,9 +79,7 @@ public final class Map { } } -/** -* Fetch value from JSON dictionary, loop through them until we reach the desired object. -*/ +/// Fetch value from JSON dictionary, loop through them until we reach the desired object. private func valueFor(keyPathComponents: [String], dictionary: [String : AnyObject]) -> AnyObject? { // Implement it as a tail recursive function. @@ -109,9 +104,7 @@ private func valueFor(keyPathComponents: [String], dictionary: [String : AnyObje return nil } -/** -* The Mapper class provides methods for converting Model objects to JSON and methods for converting JSON to Model objects -*/ +/// The Mapper class provides methods for converting Model objects to JSON and methods for converting JSON to Model objects public final class Mapper { public init(){ @@ -119,9 +112,7 @@ public final class Mapper { // MARK: Mapping functions that map to an existing object toObject - /** - * Map a JSON string onto an existing object - */ + /// Map a JSON string onto an existing object public func map(JSONString: String, var toObject object: N) -> N { if let JSON = parseJSONDictionary(JSONString) { return map(JSON, toObject: object) @@ -129,9 +120,7 @@ public final class Mapper { return object } - /** - * Maps a JSON object to an existing Mappable object if it is a JSON dictionary, or returns the passed object as is - */ + /// Maps a JSON object to an existing Mappable object if it is a JSON dictionary, or returns the passed object as is public func map(JSON: AnyObject?, var toObject object: N) -> N { if let JSON = JSON as? [String : AnyObject] { return map(JSON, toObject: object) @@ -140,10 +129,8 @@ public final class Mapper { return object } - /** - * Maps a JSON dictionary to an existing object that conforms to Mappable. - * Usefull for those pesky objects that have crappy designated initializers like NSManagedObject - */ + /// Maps a JSON dictionary to an existing object that conforms to Mappable. + /// Usefull for those pesky objects that have crappy designated initializers like NSManagedObject public func map(JSONDictionary: [String : AnyObject], var toObject object: N) -> N { let map = Map(mappingType: .FromJSON, JSONDictionary: JSONDictionary) object.mapping(map) @@ -152,9 +139,7 @@ public final class Mapper { //MARK: Mapping functions that create an object - /** - * Map a JSON string to an object that conforms to Mappable - */ + /// Map a JSON string to an object that conforms to Mappable public func map(JSONString: String) -> N? { if let JSON = parseJSONDictionary(JSONString) { return map(JSON) @@ -162,9 +147,7 @@ public final class Mapper { return nil } - /** - * Map a JSON NSString to an object that conforms to Mappable - */ + /// Map a JSON NSString to an object that conforms to Mappable public func map(JSONString: NSString) -> N? { if let string = JSONString as? String { return map(string) @@ -172,9 +155,7 @@ public final class Mapper { return nil } - /** - * Maps a JSON object to a Mappable object if it is a JSON dictionary or NSString, or returns nil. - */ + /// Maps a JSON object to a Mappable object if it is a JSON dictionary or NSString, or returns nil. public func map(JSON: AnyObject?) -> N? { if let JSON = JSON as? [String : AnyObject] { return map(JSON) @@ -183,9 +164,7 @@ public final class Mapper { return nil } - /** - * Maps a JSON dictionary to an object that conforms to Mappable - */ + /// Maps a JSON dictionary to an object that conforms to Mappable public func map(JSONDictionary: [String : AnyObject]) -> N? { let map = Map(mappingType: .FromJSON, JSONDictionary: JSONDictionary) let object = N(map) @@ -194,9 +173,7 @@ public final class Mapper { //MARK: Mapping functions for Arrays and Dictionaries - /** - * Maps a JSON array to an object that conforms to Mappable - */ + /// Maps a JSON array to an object that conforms to Mappable public func mapArray(JSONString: String) -> [N] { let parsedJSON: AnyObject? = parseJSONString(JSONString) @@ -213,9 +190,7 @@ public final class Mapper { return [] } - /** Maps a JSON object to an array of Mappable objects if it is an array of - * JSON dictionary, or returns nil. - */ + /// Maps a JSON object to an array of Mappable objects if it is an array of JSON dictionary, or returns nil. public func mapArray(JSON: AnyObject?) -> [N]? { if let JSONArray = JSON as? [[String : AnyObject]] { return mapArray(JSONArray) @@ -224,17 +199,13 @@ public final class Mapper { return nil } - /** - * Maps an array of JSON dictionary to an array of object that conforms to Mappable - */ + /// Maps an array of JSON dictionary to an array of Mappable objects public func mapArray(JSONArray: [[String : AnyObject]]) -> [N] { // map every element in JSON array to type N return JSONArray.filterMap(map) } - /** Maps a JSON object to a dictionary of Mappable objects if it is a JSON - * dictionary of dictionaries, or returns nil. - */ + /// Maps a JSON object to a dictionary of Mappable objects if it is a JSON dictionary of dictionaries, or returns nil. public func mapDictionary(JSON: AnyObject?) -> [String : N]? { if let JSONDictionary = JSON as? [String : [String : AnyObject]] { return mapDictionary(JSONDictionary) @@ -243,28 +214,37 @@ public final class Mapper { return nil } - /** - * Maps a JSON dictionary of dictionaries to a dictionary of objects that conform to Mappable. - */ + /// Maps a JSON dictionary of dictionaries to a dictionary of Mappble objects public func mapDictionary(JSONDictionary: [String : [String : AnyObject]]) -> [String : N] { // map every value in dictionary to type N return JSONDictionary.filterMap(map) } + + /// Maps a JSON object to a dictionary of arrays of Mappable objects + public func mapDictionaryOfArrays(JSON: AnyObject?) -> [String : [N]]? { + if let JSONDictionary = JSON as? [String : [[String : AnyObject]]] { + return mapDictionaryOfArrays(JSONDictionary) + } + + return nil + } + + ///Maps a JSON dictionary of arrays to a dictionary of arrays of Mappable objects + public func mapDictionaryOfArrays(JSONDictionary: [String : [[String : AnyObject]]]) -> [String : [N]] { + // map every value in dictionary to type N + return JSONDictionary.filterMap({ mapArray($0) }) + } // MARK: Functions that create JSON from objects - /** - * Maps an object that conforms to Mappable to a JSON dictionary - */ + ///Maps an object that conforms to Mappable to a JSON dictionary public func toJSON(var object: N) -> [String : AnyObject] { let map = Map(mappingType: .ToJSON, JSONDictionary: [:]) object.mapping(map) return map.JSONDictionary } - /** - * Maps an array of Objects to an array of JSON dictionaries [[String : AnyObject]] - */ + ///Maps an array of Objects to an array of JSON dictionaries [[String : AnyObject]] public func toJSONArray(array: [N]) -> [[String : AnyObject]] { return array.map { // convert every element in array to JSON dictionary equivalent @@ -272,19 +252,23 @@ public final class Mapper { } } - /** - * Maps a dictionary of Objects that conform to Mappable to a JSON dictionary of dictionaries. - */ + ///Maps a dictionary of Objects that conform to Mappable to a JSON dictionary of dictionaries. public func toJSONDictionary(dictionary: [String : N]) -> [String : [String : AnyObject]] { return dictionary.map { k, v in // convert every value in dictionary to its JSON dictionary equivalent return (k, self.toJSON(v)) } } + + ///Maps a dictionary of Objects that conform to Mappable to a JSON dictionary of dictionaries. + public func toJSONDictionaryOfArrays(dictionary: [String : [N]]) -> [String : [[String : AnyObject]]] { + return dictionary.map { k, v in + // convert every value (array) in dictionary to its JSON dictionary equivalent + return (k, self.toJSONArray(v)) + } + } - /** - * Maps an Object to a JSON string - */ + /// Maps an Object to a JSON string public func toJSONString(object: N, prettyPrint: Bool) -> String? { let JSONDict = toJSON(object) @@ -306,17 +290,13 @@ public final class Mapper { // MARK: Private utility functions for converting strings to JSON objects - /** - * Convert a JSON String into a Dictionary using NSJSONSerialization - */ + /// Convert a JSON String into a Dictionary using NSJSONSerialization private func parseJSONDictionary(JSON: String) -> [String : AnyObject]? { let parsedJSON: AnyObject? = parseJSONString(JSON) return parseJSONDictionary(parsedJSON) } - - /** - * Convert a JSON Object into a Dictionary using NSJSONSerialization - */ + + /// Convert a JSON Object into a Dictionary using NSJSONSerialization private func parseJSONDictionary(JSON: AnyObject?) -> [String : AnyObject]? { if let JSONDict = JSON as? [String : AnyObject] { return JSONDict @@ -325,9 +305,7 @@ public final class Mapper { return nil } - /** - * Convert a JSON String into an Object using NSJSONSerialization - */ + /// Convert a JSON String into an Object using NSJSONSerialization private func parseJSONString(JSON: String) -> AnyObject? { let data = JSON.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true) if let data = data { @@ -366,6 +344,18 @@ extension Dictionary { return mapped } + internal func map(@noescape f: Element -> (K, [V])) -> [K : [V]] { + var mapped = [K : [V]]() + + for element in self { + let newElement = f(element) + mapped[newElement.0] = newElement.1 + } + + return mapped + } + + internal func filterMap(@noescape f: Value -> U?) -> [Key : U] { var mapped = [Key : U]() diff --git a/ObjectMapper/Core/Operators.swift b/ObjectMapper/Core/Operators.swift index 8f1076a3..eac39d13 100755 --- a/ObjectMapper/Core/Operators.swift +++ b/ObjectMapper/Core/Operators.swift @@ -15,10 +15,8 @@ infix operator <- {} // MARK:- Objects with Basic types -/** -* Object of Basic type -*/ +/// Object of Basic type public func <- (inout left: T, right: Map) { if right.mappingType == MappingType.FromJSON { FromJSON.basicType(&left, object: right.value()) @@ -27,9 +25,7 @@ public func <- (inout left: T, right: Map) { } } -/** -* Optional object of basic type -*/ +/// Optional object of basic type public func <- (inout left: T?, right: Map) { if right.mappingType == MappingType.FromJSON { FromJSON.optionalBasicType(&left, object: right.value()) @@ -38,9 +34,7 @@ public func <- (inout left: T?, right: Map) { } } -/** -* Implicitly unwrapped optional object of basic type -*/ +/// Implicitly unwrapped optional object of basic type public func <- (inout left: T!, right: Map) { if right.mappingType == MappingType.FromJSON { FromJSON.optionalBasicType(&left, object: right.value()) @@ -50,76 +44,59 @@ public func <- (inout left: T!, right: Map) { } // MARK:- Raw Representable types -/** -* Object of Raw Representable type -*/ + +/// Object of Raw Representable type public func <- (inout left: T, right: Map) { left <- (right, EnumTransform()) } -/** -* Optional Object of Raw Representable type -*/ +/// Optional Object of Raw Representable type public func <- (inout left: T?, right: Map) { left <- (right, EnumTransform()) } -/** -* Implicitly Unwrapped Optional Object of Raw Representable type -*/ +/// Implicitly Unwrapped Optional Object of Raw Representable type public func <- (inout left: T!, right: Map) { left <- (right, EnumTransform()) } // MARK:- Arrays of Raw Representable type -/** -* Array of Raw Representable object -*/ + +/// Array of Raw Representable object public func <- (inout left: [T], right: Map) { left <- (right, EnumTransform()) } -/** -* Array of Raw Representable object -*/ +/// Array of Raw Representable object public func <- (inout left: [T]?, right: Map) { left <- (right, EnumTransform()) } -/** -* Array of Raw Representable object -*/ +/// Array of Raw Representable object public func <- (inout left: [T]!, right: Map) { left <- (right, EnumTransform()) } // MARK:- Dictionaries of Raw Representable type -/** -* Dictionary of Raw Representable object -*/ + +/// Dictionary of Raw Representable object public func <- (inout left: [String: T], right: Map) { left <- (right, EnumTransform()) } -/** -* Dictionary of Raw Representable object -*/ +/// Dictionary of Raw Representable object public func <- (inout left: [String: T]?, right: Map) { left <- (right, EnumTransform()) } -/** -* Dictionary of Raw Representable object -*/ +/// Dictionary of Raw Representable object public func <- (inout left: [String: T]!, right: Map) { left <- (right, EnumTransform()) } // MARK:- Transforms -/** -* Object of Basic type with Transform -*/ +/// Object of Basic type with Transform public func <- (inout left: T, right: (Map, Transform)) { if right.0.mappingType == MappingType.FromJSON { var value: T? = right.1.transformFromJSON(right.0.currentValue) @@ -130,9 +107,7 @@ public func <- (inout l } } -/** -* Optional object of basic type with Transform -*/ +/// Optional object of basic type with Transform public func <- (inout left: T?, right: (Map, Transform)) { if right.0.mappingType == MappingType.FromJSON { var value: T? = right.1.transformFromJSON(right.0.currentValue) @@ -143,9 +118,7 @@ public func <- (inout l } } -/** -* Implicitly unwrapped optional object of basic type with Transform -*/ +/// Implicitly unwrapped optional object of basic type with Transform public func <- (inout left: T!, right: (Map, Transform)) { if right.0.mappingType == MappingType.FromJSON { var value: T? = right.1.transformFromJSON(right.0.currentValue) @@ -261,9 +234,8 @@ private func toJSONDictionaryWithTransform(input: [String: T.O } // MARK:- Mappable Objects - -/** -* Object conforming to Mappable -*/ + +/// Object conforming to Mappable public func <- (inout left: T, right: Map) { if right.mappingType == MappingType.FromJSON { FromJSON.object(&left, object: right.currentValue) @@ -272,9 +244,7 @@ public func <- (inout left: T, right: Map) { } } -/** -* Optional Mappable objects -*/ +/// Optional Mappable objects public func <- (inout left: T?, right: Map) { if right.mappingType == MappingType.FromJSON { FromJSON.optionalObject(&left, object: right.currentValue) @@ -283,9 +253,7 @@ public func <- (inout left: T?, right: Map) { } } -/** -* Implicitly unwrapped optional Mappable objects -*/ +/// Implicitly unwrapped optional Mappable objects public func <- (inout left: T!, right: Map) { if right.mappingType == MappingType.FromJSON { FromJSON.optionalObject(&left, object: right.currentValue) @@ -295,9 +263,8 @@ public func <- (inout left: T!, right: Map) { } // MARK:- Dictionary of Mappable objects - Dictionary -/** -* Dictionary of Mappable objects -*/ + +/// Dictionary of Mappable objects public func <- (inout left: Dictionary, right: Map) { if right.mappingType == MappingType.FromJSON { FromJSON.objectDictionary(&left, object: right.currentValue) @@ -306,9 +273,7 @@ public func <- (inout left: Dictionary, right: Map) { } } -/** -* Optional Dictionary of Mappable object -*/ +/// Optional Dictionary of Mappable object public func <- (inout left: Dictionary?, right: Map) { if right.mappingType == MappingType.FromJSON { FromJSON.optionalObjectDictionary(&left, object: right.currentValue) @@ -317,9 +282,7 @@ public func <- (inout left: Dictionary?, right: Map) { } } -/** -* Implicitly unwrapped Optional Dictionary of Mappable object -*/ +/// Implicitly unwrapped Optional Dictionary of Mappable object public func <- (inout left: Dictionary!, right: Map) { if right.mappingType == MappingType.FromJSON { FromJSON.optionalObjectDictionary(&left, object: right.currentValue) @@ -328,10 +291,36 @@ public func <- (inout left: Dictionary!, right: Map) { } } +/// Dictionary of Mappable objects +public func <- (inout left: Dictionary, right: Map) { + if right.mappingType == MappingType.FromJSON { + FromJSON.objectDictionaryOfArrays(&left, object: right.currentValue) + } else { + ToJSON.objectDictionaryOfArrays(left, key: right.currentKey!, dictionary: &right.JSONDictionary) + } +} + +/// Optional Dictionary of Mappable object +public func <- (inout left: Dictionary?, right: Map) { + if right.mappingType == MappingType.FromJSON { + FromJSON.optionalObjectDictionaryOfArrays(&left, object: right.currentValue) + } else { + ToJSON.optionalObjectDictionaryOfArrays(left, key: right.currentKey!, dictionary: &right.JSONDictionary) + } +} + +/// Implicitly unwrapped Optional Dictionary of Mappable object +public func <- (inout left: Dictionary!, right: Map) { + if right.mappingType == MappingType.FromJSON { + FromJSON.optionalObjectDictionaryOfArrays(&left, object: right.currentValue) + } else { + ToJSON.optionalObjectDictionaryOfArrays(left, key: right.currentKey!, dictionary: &right.JSONDictionary) + } +} + // MARK:- Array of Mappable objects - Array -/** -* Array of Mappable objects -*/ + +/// Array of Mappable objects public func <- (inout left: Array, right: Map) { if right.mappingType == MappingType.FromJSON { FromJSON.objectArray(&left, object: right.currentValue) @@ -340,9 +329,7 @@ public func <- (inout left: Array, right: Map) { } } -/** -* Optional array of Mappable objects -*/ +/// Optional array of Mappable objects public func <- (inout left: Array?, right: Map) { if right.mappingType == MappingType.FromJSON { FromJSON.optionalObjectArray(&left, object: right.currentValue) @@ -351,9 +338,7 @@ public func <- (inout left: Array?, right: Map) { } } -/** -* Implicitly unwrapped Optional array of Mappable objects -*/ +/// Implicitly unwrapped Optional array of Mappable objects public func <- (inout left: Array!, right: Map) { if right.mappingType == MappingType.FromJSON { FromJSON.optionalObjectArray(&left, object: right.currentValue) diff --git a/ObjectMapper/Core/ToJSON.swift b/ObjectMapper/Core/ToJSON.swift index 3c7e3871..fee8e0d2 100644 --- a/ObjectMapper/Core/ToJSON.swift +++ b/ObjectMapper/Core/ToJSON.swift @@ -136,4 +136,18 @@ internal final class ToJSON { objectDictionary(field, key: key, dictionary: &dictionary) } } + + class func objectDictionaryOfArrays(field: Dictionary, key: String, inout dictionary: [String : AnyObject]) { + let JSONObjects = Mapper().toJSONDictionaryOfArrays(field) + + if !JSONObjects.isEmpty { + setValue(JSONObjects, forKey: key, dictionary: &dictionary) + } + } + + class func optionalObjectDictionaryOfArrays(field: Dictionary?, key: String, inout dictionary: [String : AnyObject]) { + if let field = field { + objectDictionaryOfArrays(field, key: key, dictionary: &dictionary) + } + } } diff --git a/ObjectMapperTests/ObjectMapperTests.swift b/ObjectMapperTests/ObjectMapperTests.swift index 3317cf2e..95c600a2 100644 --- a/ObjectMapperTests/ObjectMapperTests.swift +++ b/ObjectMapperTests/ObjectMapperTests.swift @@ -275,12 +275,35 @@ class ObjectMapperTests: XCTestCase { expect(tasks?[1].percentage).to(equal(percentage2)) } + func testDictionaryOfArrayOfCustomObjects(){ + let percentage1: Double = 0.1 + let percentage2: Double = 1792.41 + + let JSONString = "{ \"dictionaryOfTasks\": { \"mondayTasks\" :[{\"taskId\":103,\"percentage\":\(percentage1)},{\"taskId\":108,\"percentage\":\(percentage2)}] } }" + + let plan = Mapper().map(JSONString) + + let dictionaryOfTasks = plan?.dictionaryOfTasks + expect(dictionaryOfTasks).notTo(beNil()) + expect(dictionaryOfTasks?["mondayTasks"]?[0].percentage).to(equal(percentage1)) + expect(dictionaryOfTasks?["mondayTasks"]?[1].percentage).to(equal(percentage2)) + + let planToJSON = Mapper().toJSONString(plan!, prettyPrint: false) + //println(planToJSON) + let planFromJSON = Mapper().map(planToJSON!) + + let dictionaryOfTasks2 = planFromJSON?.dictionaryOfTasks + expect(dictionaryOfTasks2).notTo(beNil()) + expect(dictionaryOfTasks2?["mondayTasks"]?[0].percentage).to(equal(percentage1)) + expect(dictionaryOfTasks2?["mondayTasks"]?[1].percentage).to(equal(percentage2)) + } + func testArrayOfEnumObjects(){ - let a: ExampleEnum = .A - let b: ExampleEnum = .B - let c: ExampleEnum = .C + let a: ExampleEnum = .A + let b: ExampleEnum = .B + let c: ExampleEnum = .C - let JSONString = "{ \"enums\": [\(a.rawValue), \(b.rawValue), \(c.rawValue)] }" + let JSONString = "{ \"enums\": [\(a.rawValue), \(b.rawValue), \(c.rawValue)] }" let enumArray = Mapper().map(JSONString) let enums = enumArray?.enums @@ -482,6 +505,7 @@ class Status: Mappable { class Plan: Mappable { var tasks: [Task]? + var dictionaryOfTasks: [String: [Task]]? required init?(_ map: Map) { mapping(map) @@ -489,6 +513,7 @@ class Plan: Mappable { func mapping(map: Map) { tasks <- map["tasks"] + dictionaryOfTasks <- map["dictionaryOfTasks"] } } @@ -568,6 +593,7 @@ class User: Mappable { var friendDictionary: [String : User]? var friend: User? var friends: [User]? = [] + init() {} From b3e045fb7d46e0ab8c1334e0557bdf0f20cbc8fa Mon Sep 17 00:00:00 2001 From: Tristan Himmelman Date: Sun, 17 May 2015 12:58:22 -0400 Subject: [PATCH 2/2] updated read me --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 9c547bb5..7cc28642 100644 --- a/README.md +++ b/README.md @@ -100,6 +100,7 @@ Object mapper can map classes composed of the following types: - Object\ - Array\ - Dictionary\ +- Dictionary\\> - Optionals of all the above - Implicitly Unwrapped Optionals of the above