From f22340ed160e52337311f4ac3f5530da3557dd75 Mon Sep 17 00:00:00 2001 From: Fabio Teles Date: Thu, 3 Mar 2016 00:01:56 -0800 Subject: [PATCH 1/3] Add the ability to reset the property on the object to `nil` when converting from JSON if optional and the json node contains "null" --- ObjectMapper/Core/FromJSON.swift | 43 ++++--- ObjectMapper/Core/Map.swift | 56 +++++---- ObjectMapper/Core/Operators.swift | 188 ++++++++++++++++++------------ 3 files changed, 160 insertions(+), 127 deletions(-) diff --git a/ObjectMapper/Core/FromJSON.swift b/ObjectMapper/Core/FromJSON.swift index b3129817..a11f33c3 100755 --- a/ObjectMapper/Core/FromJSON.swift +++ b/ObjectMapper/Core/FromJSON.swift @@ -34,21 +34,17 @@ internal final class FromJSON { field = value } } - + /// optional basic type class func optionalBasicType(inout field: FieldType?, object: FieldType?) { - if let value = object { - field = value - } + field = object } - + /// Implicitly unwrapped optional basic type class func optionalBasicType(inout field: FieldType!, object: FieldType?) { - if let value = object { - field = value - } + field = object } - + /// Mappable object class func object(inout field: N, map: Map) { if map.toObject { @@ -57,43 +53,47 @@ internal final class FromJSON { field = value } } - + /// Optional Mappable Object class func optionalObject(inout field: N?, map: Map) { - if let field = field where map.toObject { + if let field = field where map.toObject && map.currentValue != nil { Mapper().map(map.currentValue, toObject: field) } else { field = Mapper().map(map.currentValue) } } - + /// Implicitly unwrapped Optional Mappable Object class func optionalObject(inout field: N!, map: Map) { - if let field = field where map.toObject { + if let field = field where map.toObject && map.currentValue != nil { Mapper().map(map.currentValue, toObject: field) } else { field = Mapper().map(map.currentValue) } } - + /// mappable object array class func objectArray(inout field: Array, map: Map) { if let objects = Mapper().mapArray(map.currentValue) { field = objects } } - + /// optional mappable object array class func optionalObjectArray(inout field: Array?, map: Map) { if let objects: Array = Mapper().mapArray(map.currentValue) { field = objects + } else { + field = nil } } - + /// Implicitly unwrapped optional mappable object array class func optionalObjectArray(inout field: Array!, map: Map) { if let objects: Array = Mapper().mapArray(map.currentValue) { field = objects + } else { + field = nil } } @@ -121,22 +121,22 @@ internal final class FromJSON { } else { if let objects = Mapper().mapDictionary(map.currentValue) { field = objects - } + } } } - + /// Optional dictionary containing Mappable objects class func optionalObjectDictionary(inout field: Dictionary?, map: Map) { - if let field = field where map.toObject { + if let field = field where map.toObject && map.currentValue != nil { Mapper().mapDictionary(map.currentValue, toDictionary: field) } else { field = Mapper().mapDictionary(map.currentValue) } } - + /// Implicitly unwrapped Dictionary containing Mappable objects class func optionalObjectDictionary(inout field: Dictionary!, map: Map) { - if let field = field where map.toObject { + if let field = field where map.toObject && map.currentValue != nil { Mapper().mapDictionary(map.currentValue, toDictionary: field) } else { field = Mapper().mapDictionary(map.currentValue) @@ -159,7 +159,6 @@ internal final class FromJSON { class func optionalObjectDictionaryOfArrays(inout field: Dictionary!, map: Map) { field = Mapper().mapDictionaryOfArrays(map.currentValue) } - /// mappable object Set class func objectSet(inout field: Set, map: Map) { diff --git a/ObjectMapper/Core/Map.swift b/ObjectMapper/Core/Map.swift index 14e9e3e1..5e6b10a8 100644 --- a/ObjectMapper/Core/Map.swift +++ b/ObjectMapper/Core/Map.swift @@ -34,10 +34,11 @@ public final class Map { public let mappingType: MappingType public internal(set) var JSONDictionary: [String : AnyObject] = [:] + public internal(set) var isKeyPresent = false public var currentValue: AnyObject? var currentKey: String? var keyIsNested = false - + let toObject: Bool // indicates whether the mapping is being applied to an existing object /// Counter for failing cases of deserializing values to `let` properties. @@ -62,13 +63,8 @@ public final class Map { currentKey = key keyIsNested = nested - // check if a value exists for the current key - if nested == false { - currentValue = JSONDictionary[key] - } else { - // break down the components of the key that are separated by . - currentValue = valueFor(ArraySlice(key.componentsSeparatedByString(".")), dictionary: JSONDictionary) - } + // break down the components of the key that are separated by . + (isKeyPresent, currentValue) = valueFor(ArraySlice(key.componentsSeparatedByString(".")), dictionary: JSONDictionary) return self } @@ -106,16 +102,16 @@ public final class Map { } /// Fetch value from JSON dictionary, loop through keyPathComponents until we reach the desired object -private func valueFor(keyPathComponents: ArraySlice, dictionary: [String: AnyObject]) -> AnyObject? { +private func valueFor(keyPathComponents: ArraySlice, dictionary: [String: AnyObject]) -> (Bool, AnyObject?) { // Implement it as a tail recursive function. if keyPathComponents.isEmpty { - return nil + return (false, nil) } if let keyPath = keyPathComponents.first { let object = dictionary[keyPath] if object is NSNull { - return nil + return (true, nil) } else if let dict = object as? [String : AnyObject] where keyPathComponents.count > 1 { let tail = keyPathComponents.dropFirst() return valueFor(tail, dictionary: dict) @@ -123,39 +119,39 @@ private func valueFor(keyPathComponents: ArraySlice, dictionary: [String let tail = keyPathComponents.dropFirst() return valueFor(tail, array: array) } else { - return object + return (object != nil, object) } } - return nil + return (false, nil) } /// Fetch value from JSON Array, loop through keyPathComponents them until we reach the desired object -private func valueFor(keyPathComponents: ArraySlice, array: [AnyObject]) -> AnyObject? { +private func valueFor(keyPathComponents: ArraySlice, array: [AnyObject]) -> (Bool, AnyObject?) { // Implement it as a tail recursive function. if keyPathComponents.isEmpty { - return nil + return (false, nil) } //Try to convert keypath to Int as index if let keyPath = keyPathComponents.first, let index = Int(keyPath) where index >= 0 && index < array.count { - - let object = array[index] - - if object is NSNull { - return nil - } else if let array = object as? [AnyObject] where keyPathComponents.count > 1 { - let tail = keyPathComponents.dropFirst() - return valueFor(tail, array: array) - } else if let dict = object as? [String : AnyObject] where keyPathComponents.count > 1 { - let tail = keyPathComponents.dropFirst() - return valueFor(tail, dictionary: dict) - } else { - return object - } + + let object = array[index] + + if object is NSNull { + return (true, nil) + } else if let array = object as? [AnyObject] where keyPathComponents.count > 1 { + let tail = keyPathComponents.dropFirst() + return valueFor(tail, array: array) + } else if let dict = object as? [String : AnyObject] where keyPathComponents.count > 1 { + let tail = keyPathComponents.dropFirst() + return valueFor(tail, dictionary: dict) + } else { + return (true, object) + } } - return nil + return (false, nil) } diff --git a/ObjectMapper/Core/Operators.swift b/ObjectMapper/Core/Operators.swift index f39b8a98..f592335a 100755 --- a/ObjectMapper/Core/Operators.swift +++ b/ObjectMapper/Core/Operators.swift @@ -39,30 +39,33 @@ infix operator <- {} /// Object of Basic type public func <- (inout left: T, right: Map) { switch right.mappingType { - case .FromJSON: + case .FromJSON where right.isKeyPresent: FromJSON.basicType(&left, object: right.value()) case .ToJSON: ToJSON.basicType(left, map: right) - } + default: () + } } /// Optional object of basic type public func <- (inout left: T?, right: Map) { switch right.mappingType { - case .FromJSON: - FromJSON.optionalBasicType(&left, object: right.value()) + case .FromJSON where right.isKeyPresent: + FromJSON.optionalBasicType(&left, object: right.value()) case .ToJSON: - ToJSON.optionalBasicType(left, map: right) - } + ToJSON.optionalBasicType(left, map: right) + default: () + } } /// Implicitly unwrapped optional object of basic type public func <- (inout left: T!, right: Map) { switch right.mappingType { - case .FromJSON: + case .FromJSON where right.isKeyPresent: FromJSON.optionalBasicType(&left, object: right.value()) case .ToJSON: ToJSON.optionalBasicType(left, map: right) + default: () } } @@ -123,38 +126,41 @@ public func <- (inout left: [String: T]!, right: Map) { public func <- (inout left: Transform.Object, right: (Map, Transform)) { let (map, transform) = right switch map.mappingType { - case .FromJSON: - let value = transform.transformFromJSON(map.currentValue) - FromJSON.basicType(&left, object: value) + case .FromJSON where map.isKeyPresent: + let value = transform.transformFromJSON(map.currentValue) + FromJSON.basicType(&left, object: value) case .ToJSON: - let value: Transform.JSON? = transform.transformToJSON(left) + let value: Transform.JSON? = transform.transformToJSON(left) ToJSON.optionalBasicType(value, map: map) - } + default: () + } } /// Optional object of basic type with Transform public func <- (inout left: Transform.Object?, right: (Map, Transform)) { let (map, transform) = right switch map.mappingType { - case .FromJSON: - let value = transform.transformFromJSON(map.currentValue) - FromJSON.optionalBasicType(&left, object: value) + case .FromJSON where map.isKeyPresent: + let value = transform.transformFromJSON(map.currentValue) + FromJSON.optionalBasicType(&left, object: value) case .ToJSON: - let value: Transform.JSON? = transform.transformToJSON(left) - ToJSON.optionalBasicType(value, map: map) - } + let value: Transform.JSON? = transform.transformToJSON(left) + ToJSON.optionalBasicType(value, map: map) + default: () + } } /// Implicitly unwrapped optional object of basic type with Transform public func <- (inout left: Transform.Object!, right: (Map, Transform)) { let (map, transform) = right switch map.mappingType { - case .FromJSON: + case .FromJSON where map.isKeyPresent: let value = transform.transformFromJSON(map.currentValue) FromJSON.optionalBasicType(&left, object: value) case .ToJSON: let value: Transform.JSON? = transform.transformToJSON(left) ToJSON.optionalBasicType(value, map: map) + default: () } } @@ -162,12 +168,13 @@ public func <- (inout left: Transform.Object!, right: public func <- (inout left: [Transform.Object], right: (Map, Transform)) { let (map, transform) = right switch map.mappingType { - case .FromJSON: + case .FromJSON where map.isKeyPresent: let values = fromJSONArrayWithTransform(map.currentValue, transform: transform) FromJSON.basicType(&left, object: values) case .ToJSON: let values = toJSONArrayWithTransform(left, transform: transform) ToJSON.optionalBasicType(values, map: map) + default: () } } @@ -175,12 +182,13 @@ public func <- (inout left: [Transform.Object], right: public func <- (inout left: [Transform.Object]?, right: (Map, Transform)) { let (map, transform) = right switch map.mappingType { - case .FromJSON: + case .FromJSON where map.isKeyPresent: let values = fromJSONArrayWithTransform(map.currentValue, transform: transform) FromJSON.optionalBasicType(&left, object: values) case .ToJSON: let values = toJSONArrayWithTransform(left, transform: transform) ToJSON.optionalBasicType(values, map: map) + default: () } } @@ -188,12 +196,13 @@ public func <- (inout left: [Transform.Object]?, right public func <- (inout left: [Transform.Object]!, right: (Map, Transform)) { let (map, transform) = right switch map.mappingType { - case .FromJSON: + case .FromJSON where map.isKeyPresent: let values = fromJSONArrayWithTransform(map.currentValue, transform: transform) FromJSON.optionalBasicType(&left, object: values) case .ToJSON: let values = toJSONArrayWithTransform(left, transform: transform) ToJSON.optionalBasicType(values, map: map) + default: () } } @@ -201,12 +210,13 @@ public func <- (inout left: [Transform.Object]!, right public func <- (inout left: [String: Transform.Object], right: (Map, Transform)) { let (map, transform) = right switch map.mappingType { - case .FromJSON: + case .FromJSON where map.isKeyPresent: let values = fromJSONDictionaryWithTransform(map.currentValue, transform: transform) FromJSON.basicType(&left, object: values) case .ToJSON: let values = toJSONDictionaryWithTransform(left, transform: transform) ToJSON.optionalBasicType(values, map: map) + default: () } } @@ -214,12 +224,13 @@ public func <- (inout left: [String: Transform.Object] public func <- (inout left: [String: Transform.Object]?, right: (Map, Transform)) { let (map, transform) = right switch map.mappingType { - case .FromJSON: + case .FromJSON where map.isKeyPresent: let values = fromJSONDictionaryWithTransform(map.currentValue, transform: transform) FromJSON.optionalBasicType(&left, object: values) case .ToJSON: let values = toJSONDictionaryWithTransform(left, transform: transform) ToJSON.optionalBasicType(values, map: map) + default: () } } @@ -227,12 +238,13 @@ public func <- (inout left: [String: Transform.Object] public func <- (inout left: [String: Transform.Object]!, right: (Map, Transform)) { let (map, transform) = right switch map.mappingType { - case .FromJSON: + case .FromJSON where map.isKeyPresent: let values = fromJSONDictionaryWithTransform(map.currentValue, transform: transform) FromJSON.optionalBasicType(&left, object: values) case .ToJSON: let values = toJSONDictionaryWithTransform(left, transform: transform) ToJSON.optionalBasicType(values, map: map) + default: () } } @@ -274,29 +286,31 @@ private func toJSONDictionaryWithTransform(input: [Str public func <- (inout left: T, right: Map) { switch right.mappingType { case .FromJSON: - FromJSON.object(&left, map: right) + FromJSON.object(&left, map: right) case .ToJSON: ToJSON.object(left, map: right) - } + } } /// Optional Mappable objects public func <- (inout left: T?, right: Map) { switch right.mappingType { - case .FromJSON: - FromJSON.optionalObject(&left, map: right) + case .FromJSON where right.isKeyPresent: + FromJSON.optionalObject(&left, map: right) case .ToJSON: ToJSON.optionalObject(left, map: right) - } + default: () + } } /// Implicitly unwrapped optional Mappable objects public func <- (inout left: T!, right: Map) { switch right.mappingType { - case .FromJSON: + case .FromJSON where right.isKeyPresent: FromJSON.optionalObject(&left, map: right) case .ToJSON: ToJSON.optionalObject(left, map: right) + default: () } } @@ -306,12 +320,13 @@ public func <- (inout left: T!, right: Map) { public func <- (inout left: Transform.Object, right: (Map, Transform)) { let (map, transform) = right switch map.mappingType { - case .FromJSON: + case .FromJSON where map.isKeyPresent: let value: Transform.Object? = transform.transformFromJSON(map.currentValue) FromJSON.basicType(&left, object: value) case .ToJSON: let value: Transform.JSON? = transform.transformToJSON(left) ToJSON.optionalBasicType(value, map: map) + default: () } } @@ -319,12 +334,13 @@ public func <- (inout public func <- (inout left: Transform.Object?, right: (Map, Transform)) { let (map, transform) = right switch map.mappingType { - case .FromJSON: + case .FromJSON where map.isKeyPresent: let value: Transform.Object? = transform.transformFromJSON(map.currentValue) FromJSON.optionalBasicType(&left, object: value) case .ToJSON: let value: Transform.JSON? = transform.transformToJSON(left) ToJSON.optionalBasicType(value, map: map) + default: () } } @@ -332,12 +348,13 @@ public func <- (inout public func <- (inout left: Transform.Object!, right: (Map, Transform)) { let (map, transform) = right switch map.mappingType { - case .FromJSON: + case .FromJSON where map.isKeyPresent: let value: Transform.Object? = transform.transformFromJSON(map.currentValue) FromJSON.optionalBasicType(&left, object: value) case .ToJSON: let value: Transform.JSON? = transform.transformToJSON(left) ToJSON.optionalBasicType(value, map: map) + default: () } } @@ -346,60 +363,66 @@ public func <- (inout /// Dictionary of Mappable objects public func <- (inout left: Dictionary, right: Map) { switch right.mappingType { - case .FromJSON: - FromJSON.objectDictionary(&left, map: right) + case .FromJSON where right.isKeyPresent: + FromJSON.objectDictionary(&left, map: right) case .ToJSON: - ToJSON.objectDictionary(left, map: right) - } + ToJSON.objectDictionary(left, map: right) + default: () + } } /// Optional Dictionary of Mappable object public func <- (inout left: Dictionary?, right: Map) { switch right.mappingType { - case .FromJSON: - FromJSON.optionalObjectDictionary(&left, map: right) + case .FromJSON where right.isKeyPresent: + FromJSON.optionalObjectDictionary(&left, map: right) case .ToJSON: - ToJSON.optionalObjectDictionary(left, map: right) - } + ToJSON.optionalObjectDictionary(left, map: right) + default: () + } } /// Implicitly unwrapped Optional Dictionary of Mappable object public func <- (inout left: Dictionary!, right: Map) { switch right.mappingType { - case .FromJSON: + case .FromJSON where right.isKeyPresent: FromJSON.optionalObjectDictionary(&left, map: right) case .ToJSON: ToJSON.optionalObjectDictionary(left, map: right) + default: () } } /// Dictionary of Mappable objects public func <- (inout left: Dictionary, right: Map) { switch right.mappingType { - case .FromJSON: + case .FromJSON where right.isKeyPresent: FromJSON.objectDictionaryOfArrays(&left, map: right) case .ToJSON: ToJSON.objectDictionaryOfArrays(left, map: right) + default: () } } /// Optional Dictionary of Mappable object public func <- (inout left: Dictionary?, right: Map) { switch right.mappingType { - case .FromJSON: + case .FromJSON where right.isKeyPresent: FromJSON.optionalObjectDictionaryOfArrays(&left, map: right) case .ToJSON: ToJSON.optionalObjectDictionaryOfArrays(left, map: right) + default: () } } /// Implicitly unwrapped Optional Dictionary of Mappable object public func <- (inout left: Dictionary!, right: Map) { switch right.mappingType { - case .FromJSON: + case .FromJSON where right.isKeyPresent: FromJSON.optionalObjectDictionaryOfArrays(&left, map: right) case .ToJSON: ToJSON.optionalObjectDictionaryOfArrays(left, map: right) + default: () } } @@ -408,7 +431,7 @@ public func <- (inout left: Dictionary!, right: Map) { /// Dictionary of Mappable objects with a transform public func <- (inout left: Dictionary, right: (Map, Transform)) { let (map, transform) = right - if let object = map.currentValue as? [String : AnyObject] where map.mappingType == .FromJSON { + if let object = map.currentValue as? [String : AnyObject] where map.mappingType == .FromJSON && map.isKeyPresent { let value = fromJSONDictionaryWithTransform(object, transform: transform) ?? left FromJSON.basicType(&left, object: value) } else if map.mappingType == .ToJSON { @@ -420,7 +443,7 @@ public func <- (inout /// Optional Dictionary of Mappable object with a transform public func <- (inout left: Dictionary?, right: (Map, Transform)) { let (map, transform) = right - if let object = map.currentValue as? [String : AnyObject] where map.mappingType == .FromJSON { + if let object = map.currentValue as? [String : AnyObject] where map.mappingType == .FromJSON && map.isKeyPresent { let value = fromJSONDictionaryWithTransform(object, transform: transform) ?? left FromJSON.optionalBasicType(&left, object: value) } else if map.mappingType == .ToJSON { @@ -432,7 +455,7 @@ public func <- (inout /// Implicitly unwrapped Optional Dictionary of Mappable object with a transform public func <- (inout left: Dictionary!, right: (Map, Transform)) { let (map, transform) = right - if let dictionary = map.currentValue as? [String : AnyObject] where map.mappingType == .FromJSON { + if let dictionary = map.currentValue as? [String : AnyObject] where map.mappingType == .FromJSON && map.isKeyPresent { let transformedDictionary = fromJSONDictionaryWithTransform(dictionary, transform: transform) ?? left FromJSON.optionalBasicType(&left, object: transformedDictionary) } else if map.mappingType == .ToJSON { @@ -444,7 +467,7 @@ public func <- (inout /// Dictionary of Mappable objects with a transform public func <- (inout left: Dictionary, right: (Map, Transform)) { let (map, transform) = right - if let dictionary = map.currentValue as? [String : [AnyObject]] where map.mappingType == .FromJSON { + if let dictionary = map.currentValue as? [String : [AnyObject]] where map.mappingType == .FromJSON && map.isKeyPresent { let transformedDictionary = dictionary.map { (key, values) in return (key, fromJSONArrayWithTransform(values, transform: transform) ?? left[key] ?? []) } @@ -453,7 +476,7 @@ public func <- (inout let transformedDictionary = left.map { (key, values) in return (key, toJSONArrayWithTransform(values, transform: transform) ?? []) } - + ToJSON.basicType(transformedDictionary, map: map) } } @@ -461,7 +484,7 @@ public func <- (inout /// Optional Dictionary of Mappable object with a transform public func <- (inout left: Dictionary?, right: (Map, Transform)) { let (map, transform) = right - if let dictionary = map.currentValue as? [String : [AnyObject]] where map.mappingType == .FromJSON { + if let dictionary = map.currentValue as? [String : [AnyObject]] where map.mappingType == .FromJSON && map.isKeyPresent { let transformedDictionary = dictionary.map { (key, values) in return (key, fromJSONArrayWithTransform(values, transform: transform) ?? left?[key] ?? []) } @@ -478,7 +501,7 @@ public func <- (inout /// Implicitly unwrapped Optional Dictionary of Mappable object with a transform public func <- (inout left: Dictionary!, right: (Map, Transform)) { let (map, transform) = right - if let dictionary = map.currentValue as? [String : [AnyObject]] where map.mappingType == .FromJSON { + if let dictionary = map.currentValue as? [String : [AnyObject]] where map.mappingType == .FromJSON && map.isKeyPresent { let transformedDictionary = dictionary.map { (key, values) in return (key, fromJSONArrayWithTransform(values, transform: transform) ?? left?[key] ?? []) } @@ -497,30 +520,33 @@ public func <- (inout /// Array of Mappable objects public func <- (inout left: Array, right: Map) { switch right.mappingType { - case .FromJSON: - FromJSON.objectArray(&left, map: right) + case .FromJSON where right.isKeyPresent: + FromJSON.objectArray(&left, map: right) case .ToJSON: ToJSON.objectArray(left, map: right) - } + default: () + } } /// Optional array of Mappable objects public func <- (inout left: Array?, right: Map) { switch right.mappingType { - case .FromJSON: - FromJSON.optionalObjectArray(&left, map: right) + case .FromJSON where right.isKeyPresent: + FromJSON.optionalObjectArray(&left, map: right) case .ToJSON: ToJSON.optionalObjectArray(left, map: right) - } + default: () + } } /// Implicitly unwrapped Optional array of Mappable objects public func <- (inout left: Array!, right: Map) { switch right.mappingType { - case .FromJSON: + case .FromJSON where right.isKeyPresent: FromJSON.optionalObjectArray(&left, map: right) case .ToJSON: ToJSON.optionalObjectArray(left, map: right) + default: () } } @@ -530,13 +556,14 @@ public func <- (inout left: Array!, right: Map) { public func <- (inout left: Array, right: (Map, Transform)) { let (map, transform) = right switch map.mappingType { - case .FromJSON: + case .FromJSON where map.isKeyPresent: if let transformedValues = fromJSONArrayWithTransform(map.currentValue, transform: transform) { FromJSON.basicType(&left, object: transformedValues) } case .ToJSON: let transformedValues = toJSONArrayWithTransform(left, transform: transform) ToJSON.optionalBasicType(transformedValues, map: map) + default: () } } @@ -544,12 +571,13 @@ public func <- (inout public func <- (inout left: Array?, right: (Map, Transform)) { let (map, transform) = right switch map.mappingType { - case .FromJSON: + case .FromJSON where map.isKeyPresent: let transformedValues = fromJSONArrayWithTransform(map.currentValue, transform: transform) FromJSON.optionalBasicType(&left, object: transformedValues) case .ToJSON: let transformedValues = toJSONArrayWithTransform(left, transform: transform) ToJSON.optionalBasicType(transformedValues, map: map) + default: () } } @@ -557,12 +585,13 @@ public func <- (inout public func <- (inout left: Array!, right: (Map, Transform)) { let (map, transform) = right switch map.mappingType { - case .FromJSON: + case .FromJSON where map.isKeyPresent: let transformedValues = fromJSONArrayWithTransform(map.currentValue, transform: transform) FromJSON.optionalBasicType(&left, object: transformedValues) case .ToJSON: let transformedValues = toJSONArrayWithTransform(left, transform: transform) ToJSON.optionalBasicType(transformedValues, map: map) + default: () } } @@ -571,30 +600,33 @@ public func <- (inout /// Array of Array Mappable objects public func <- (inout left: Array>, right: Map) { switch right.mappingType { - case .FromJSON: + case .FromJSON where right.isKeyPresent: FromJSON.twoDimensionalObjectArray(&left, map: right) case .ToJSON: ToJSON.twoDimensionalObjectArray(left, map: right) + default: () } } /// Optional array of Mappable objects public func <- (inout left:Array>?, right: Map) { switch right.mappingType { - case .FromJSON: + case .FromJSON where right.isKeyPresent: FromJSON.optionalTwoDimensionalObjectArray(&left, map: right) case .ToJSON: ToJSON.optionalTwoDimensionalObjectArray(left, map: right) + default: () } } /// Implicitly unwrapped Optional array of Mappable objects public func <- (inout left: Array>!, right: Map) { switch right.mappingType { - case .FromJSON: + case .FromJSON where right.isKeyPresent: FromJSON.optionalTwoDimensionalObjectArray(&left, map: right) case .ToJSON: ToJSON.optionalTwoDimensionalObjectArray(left, map: right) + default: () } } @@ -603,7 +635,7 @@ public func <- (inout left: Array>!, right: Map) { /// Array of Array Mappable objects with transform public func <- (inout left: Array>, right: (Map, Transform)) { let (map, transform) = right - if let original2DArray = map.currentValue as? [[AnyObject]] where map.mappingType == .FromJSON { + if let original2DArray = map.currentValue as? [[AnyObject]] where map.mappingType == .FromJSON && map.isKeyPresent { let transformed2DArray = original2DArray.flatMap { values in fromJSONArrayWithTransform(values, transform: transform) } @@ -619,7 +651,7 @@ public func <- (inout /// Optional array of Mappable objects with transform public func <- (inout left:Array>?, right: (Map, Transform)) { let (map, transform) = right - if let original2DArray = map.currentValue as? [[AnyObject]] where map.mappingType == .FromJSON { + if let original2DArray = map.currentValue as? [[AnyObject]] where map.mappingType == .FromJSON && map.isKeyPresent { let transformed2DArray = original2DArray.flatMap { values in fromJSONArrayWithTransform(values, transform: transform) } @@ -635,7 +667,7 @@ public func <- (inout /// Implicitly unwrapped Optional array of Mappable objects with transform public func <- (inout left: Array>!, right: (Map, Transform)) { let (map, transform) = right - if let original2DArray = map.currentValue as? [[AnyObject]] where map.mappingType == .FromJSON { + if let original2DArray = map.currentValue as? [[AnyObject]] where map.mappingType == .FromJSON && map.isKeyPresent { let transformed2DArray = original2DArray.flatMap { values in fromJSONArrayWithTransform(values, transform: transform) } @@ -653,10 +685,11 @@ public func <- (inout /// Set of Mappable objects public func <- (inout left: Set, right: Map) { switch right.mappingType { - case .FromJSON: + case .FromJSON where right.isKeyPresent: FromJSON.objectSet(&left, map: right) case .ToJSON: ToJSON.objectSet(left, map: right) + default: () } } @@ -664,20 +697,22 @@ public func <- (inout left: Set, right: Map) { /// Optional Set of Mappable objects public func <- (inout left: Set?, right: Map) { switch right.mappingType { - case .FromJSON: + case .FromJSON where right.isKeyPresent: FromJSON.optionalObjectSet(&left, map: right) case .ToJSON: ToJSON.optionalObjectSet(left, map: right) + default: () } } /// Implicitly unwrapped Optional Set of Mappable objects public func <- (inout left: Set!, right: Map) { switch right.mappingType { - case .FromJSON: + case .FromJSON where right.isKeyPresent: FromJSON.optionalObjectSet(&left, map: right) case .ToJSON: ToJSON.optionalObjectSet(left, map: right) + default: () } } @@ -688,13 +723,14 @@ public func <- (inout left: Set!, right: Map) public func <- >(inout left: Set, right: (Map, Transform)) { let (map, transform) = right switch map.mappingType { - case .FromJSON: + case .FromJSON where map.isKeyPresent: if let transformedValues = fromJSONArrayWithTransform(map.currentValue, transform: transform) { FromJSON.basicType(&left, object: Set(transformedValues)) } case .ToJSON: let transformedValues = toJSONArrayWithTransform(Array(left), transform: transform) ToJSON.optionalBasicType(transformedValues, map: map) + default: () } } @@ -703,7 +739,7 @@ public func <- >(inout left: Set?, right: (Map, Transform)) { let (map, transform) = right switch map.mappingType { - case .FromJSON: + case .FromJSON where map.isKeyPresent: if let transformedValues = fromJSONArrayWithTransform(map.currentValue, transform: transform) { FromJSON.basicType(&left, object: Set(transformedValues)) } @@ -712,6 +748,7 @@ public func <- >(inout left: Set!, right: (Map, Transform)) { let (map, transform) = right switch map.mappingType { - case .FromJSON: + case .FromJSON where map.isKeyPresent: if let transformedValues = fromJSONArrayWithTransform(map.currentValue, transform: transform) { FromJSON.basicType(&left, object: Set(transformedValues)) } @@ -728,5 +765,6 @@ public func <- Date: Tue, 22 Mar 2016 23:16:53 -0700 Subject: [PATCH 2/3] Revert nested check for performance reasons --- ObjectMapper/Core/Map.swift | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/ObjectMapper/Core/Map.swift b/ObjectMapper/Core/Map.swift index 5e6b10a8..0bb64267 100644 --- a/ObjectMapper/Core/Map.swift +++ b/ObjectMapper/Core/Map.swift @@ -62,9 +62,17 @@ public final class Map { // save key and value associated to it currentKey = key keyIsNested = nested - - // break down the components of the key that are separated by . - (isKeyPresent, currentValue) = valueFor(ArraySlice(key.componentsSeparatedByString(".")), dictionary: JSONDictionary) + + // check if a value exists for the current key + // do this pre-check for performance reasons + if nested == false { + let object = JSONDictionary[key], isNSNull = object is NSNull + isKeyPresent = isNSNull ? true : object != nil + currentValue = isNSNull ? nil : object + } else { + // break down the components of the key that are separated by . + (isKeyPresent, currentValue) = valueFor(ArraySlice(key.componentsSeparatedByString(".")), dictionary: JSONDictionary) + } return self } From fac35389d2b6b4f9d773a9709f79a23ff782c390 Mon Sep 17 00:00:00 2001 From: Fabio Teles Date: Tue, 22 Mar 2016 23:17:11 -0700 Subject: [PATCH 3/3] Add unit tests for nullable JSON mapping --- ObjectMapper.xcodeproj/project.pbxproj | 8 ++ .../NullableKeysFromJSONTests.swift | 118 ++++++++++++++++++ 2 files changed, 126 insertions(+) create mode 100644 ObjectMapperTests/NullableKeysFromJSONTests.swift diff --git a/ObjectMapper.xcodeproj/project.pbxproj b/ObjectMapper.xcodeproj/project.pbxproj index 66713c16..0f686d2e 100644 --- a/ObjectMapper.xcodeproj/project.pbxproj +++ b/ObjectMapper.xcodeproj/project.pbxproj @@ -108,6 +108,9 @@ CD50B6FD1A82518300744312 /* TransformType.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD50B6FC1A82518300744312 /* TransformType.swift */; }; CD71C8C11A7218AD009D4161 /* TransformOf.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD71C8C01A7218AD009D4161 /* TransformOf.swift */; }; D86BDEA41A51E5AD00120819 /* ISO8601DateTransform.swift in Sources */ = {isa = PBXBuildFile; fileRef = D86BDEA31A51E5AC00120819 /* ISO8601DateTransform.swift */; }; + DC99C8CC1CA261A8005C788C /* NullableKeysFromJSONTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC99C8CB1CA261A8005C788C /* NullableKeysFromJSONTests.swift */; }; + DC99C8CD1CA261AD005C788C /* NullableKeysFromJSONTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC99C8CB1CA261A8005C788C /* NullableKeysFromJSONTests.swift */; }; + DC99C8CE1CA261AE005C788C /* NullableKeysFromJSONTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC99C8CB1CA261A8005C788C /* NullableKeysFromJSONTests.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -196,6 +199,7 @@ CD50B6FC1A82518300744312 /* TransformType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransformType.swift; sourceTree = ""; }; CD71C8C01A7218AD009D4161 /* TransformOf.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = TransformOf.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; D86BDEA31A51E5AC00120819 /* ISO8601DateTransform.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ISO8601DateTransform.swift; sourceTree = ""; }; + DC99C8CB1CA261A8005C788C /* NullableKeysFromJSONTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NullableKeysFromJSONTests.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -320,6 +324,7 @@ 6A412A231BB0DA26001C3F67 /* PerformanceTests.swift */, CD44374C1AAE9C1100A271BA /* NestedKeysTests.swift */, 6100B1BF1BD76A020011114A /* NestedArrayTests.swift */, + DC99C8CB1CA261A8005C788C /* NullableKeysFromJSONTests.swift */, 6AAC8F8519F03C2900E7A677 /* ObjectMapperTests.swift */, 3BAD2C0F1BDDC0B000E6B203 /* MappableExtensionsTests.swift */, 6A0BF1FE1C0B53470083D1AF /* ToObjectTests.swift */, @@ -673,6 +678,7 @@ 6AC692441BE3FD45004C119A /* CustomTransformTests.swift in Sources */, 6AC692451BE3FD45004C119A /* NestedKeysTests.swift in Sources */, 6AC692461BE3FD45004C119A /* NestedArrayTests.swift in Sources */, + DC99C8CE1CA261AE005C788C /* NullableKeysFromJSONTests.swift in Sources */, 6AC692471BE3FD45004C119A /* ObjectMapperTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -734,6 +740,7 @@ 6A6AEB981A9387D0002573D3 /* BasicTypes.swift in Sources */, 6A412A241BB0DA26001C3F67 /* PerformanceTests.swift in Sources */, 6AAC8F8619F03C2900E7A677 /* ObjectMapperTests.swift in Sources */, + DC99C8CC1CA261A8005C788C /* NullableKeysFromJSONTests.swift in Sources */, 6100B1C01BD76A030011114A /* NestedArrayTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -774,6 +781,7 @@ CD16032A1AC02480000CD69A /* ObjectMapperTests.swift in Sources */, CD1603271AC02480000CD69A /* BasicTypesTestsToJSON.swift in Sources */, 6A412A251BB0DA26001C3F67 /* PerformanceTests.swift in Sources */, + DC99C8CD1CA261AD005C788C /* NullableKeysFromJSONTests.swift in Sources */, CD1603281AC02480000CD69A /* CustomTransformTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/ObjectMapperTests/NullableKeysFromJSONTests.swift b/ObjectMapperTests/NullableKeysFromJSONTests.swift new file mode 100644 index 00000000..7275fa31 --- /dev/null +++ b/ObjectMapperTests/NullableKeysFromJSONTests.swift @@ -0,0 +1,118 @@ +// +// NullableKeysFromJSONTests.swift +// ObjectMapper +// +// Created by Fabio Teles on 3/22/16. +// +// The MIT License (MIT) +// +// Copyright (c) 2014-2015 Hearst +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation +import XCTest +import ObjectMapper + +class NullableKeysFromJSONTests: XCTestCase { + + let fullJSONString = "{\"firstName\": \"John\", \"lastName\": \"Doe\", \"team\": \"Broncos\", \"age\": 25, \"address\": {\"street\": \"Nothing Ave\", \"number\": 101, \"city\": \"Los Angeles\"} }" + let nullJSONString = "{\"firstName\": \"John\", \"lastName\": null, \"team\": \"Broncos\", \"age\": null, \"address\": {\"street\": \"Nothing Ave\", \"number\": 101, \"city\": null} }" + let absentJSONString = "{\"firstName\": \"John\", \"team\": \"Broncos\", \"address\": {\"street\": \"Nothing Ave\", \"number\": 102} }" + + let mapper = Mapper() + + override func setUp() { + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + func testMapperNullifiesValues() { + guard let player = mapper.map(fullJSONString) else { + XCTFail("Mapping failed") + return + } + + XCTAssertNotNil(player.lastName) + XCTAssertNotNil(player.age) + XCTAssertNotNil(player.address?.city) + + mapper.map(nullJSONString, toObject: player) + + XCTAssertNotNil(player.firstName) + XCTAssertNil(player.lastName) + XCTAssertNil(player.age) + XCTAssertNil(player.address?.city) + } + + func testMapperAbsentValues() { + guard let player = mapper.map(fullJSONString) else { + XCTFail("Mapping failed") + return + } + + XCTAssertNotNil(player.lastName) + XCTAssertNotNil(player.age) + XCTAssertNotNil(player.address?.city) + + mapper.map(absentJSONString, toObject: player) + + XCTAssertNotNil(player.firstName) + XCTAssertNotNil(player.lastName) + XCTAssertNotNil(player.age) + XCTAssertNotNil(player.address?.city) + XCTAssertEqual(player.address?.number, 102) + } +} + +class Player: Mappable { + var firstName: String? + var lastName: String? + var team: String? + var age: Int? + var address: Address? + + required init?(_ map: Map){ + mapping(map) + } + + func mapping(map: Map) { + firstName <- map["firstName"] + lastName <- map["lastName"] + team <- map["team"] + age <- map["age"] + address <- map["address"] + } +} + +class Address: Mappable { + var street: String? + var number: Int? + var city: String? + + required init?(_ map: Map){ + mapping(map) + } + + func mapping(map: Map) { + street <- map["street"] + number <- map["number"] + city <- map["city"] + } +} \ No newline at end of file