Skip to content

Commit f72ad15

Browse files
Merge pull request tristanhimmelman#403 from pixel4/add-nullable-from-json
Add the ability to reset object properties to `nil` from JSON
2 parents 9e0122e + 70720a7 commit f72ad15

5 files changed

Lines changed: 291 additions & 124 deletions

File tree

ObjectMapper.xcodeproj/project.pbxproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,9 @@
108108
CD50B6FD1A82518300744312 /* TransformType.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD50B6FC1A82518300744312 /* TransformType.swift */; };
109109
CD71C8C11A7218AD009D4161 /* TransformOf.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD71C8C01A7218AD009D4161 /* TransformOf.swift */; };
110110
D86BDEA41A51E5AD00120819 /* ISO8601DateTransform.swift in Sources */ = {isa = PBXBuildFile; fileRef = D86BDEA31A51E5AC00120819 /* ISO8601DateTransform.swift */; };
111+
DC99C8CC1CA261A8005C788C /* NullableKeysFromJSONTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC99C8CB1CA261A8005C788C /* NullableKeysFromJSONTests.swift */; };
112+
DC99C8CD1CA261AD005C788C /* NullableKeysFromJSONTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC99C8CB1CA261A8005C788C /* NullableKeysFromJSONTests.swift */; };
113+
DC99C8CE1CA261AE005C788C /* NullableKeysFromJSONTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC99C8CB1CA261A8005C788C /* NullableKeysFromJSONTests.swift */; };
111114
/* End PBXBuildFile section */
112115

113116
/* Begin PBXContainerItemProxy section */
@@ -196,6 +199,7 @@
196199
CD50B6FC1A82518300744312 /* TransformType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransformType.swift; sourceTree = "<group>"; };
197200
CD71C8C01A7218AD009D4161 /* TransformOf.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = TransformOf.swift; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
198201
D86BDEA31A51E5AC00120819 /* ISO8601DateTransform.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ISO8601DateTransform.swift; sourceTree = "<group>"; };
202+
DC99C8CB1CA261A8005C788C /* NullableKeysFromJSONTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NullableKeysFromJSONTests.swift; sourceTree = "<group>"; };
199203
/* End PBXFileReference section */
200204

201205
/* Begin PBXFrameworksBuildPhase section */
@@ -320,6 +324,7 @@
320324
6A412A231BB0DA26001C3F67 /* PerformanceTests.swift */,
321325
CD44374C1AAE9C1100A271BA /* NestedKeysTests.swift */,
322326
6100B1BF1BD76A020011114A /* NestedArrayTests.swift */,
327+
DC99C8CB1CA261A8005C788C /* NullableKeysFromJSONTests.swift */,
323328
6AAC8F8519F03C2900E7A677 /* ObjectMapperTests.swift */,
324329
3BAD2C0F1BDDC0B000E6B203 /* MappableExtensionsTests.swift */,
325330
6A0BF1FE1C0B53470083D1AF /* ToObjectTests.swift */,
@@ -673,6 +678,7 @@
673678
6AC692441BE3FD45004C119A /* CustomTransformTests.swift in Sources */,
674679
6AC692451BE3FD45004C119A /* NestedKeysTests.swift in Sources */,
675680
6AC692461BE3FD45004C119A /* NestedArrayTests.swift in Sources */,
681+
DC99C8CE1CA261AE005C788C /* NullableKeysFromJSONTests.swift in Sources */,
676682
6AC692471BE3FD45004C119A /* ObjectMapperTests.swift in Sources */,
677683
);
678684
runOnlyForDeploymentPostprocessing = 0;
@@ -734,6 +740,7 @@
734740
6A6AEB981A9387D0002573D3 /* BasicTypes.swift in Sources */,
735741
6A412A241BB0DA26001C3F67 /* PerformanceTests.swift in Sources */,
736742
6AAC8F8619F03C2900E7A677 /* ObjectMapperTests.swift in Sources */,
743+
DC99C8CC1CA261A8005C788C /* NullableKeysFromJSONTests.swift in Sources */,
737744
6100B1C01BD76A030011114A /* NestedArrayTests.swift in Sources */,
738745
);
739746
runOnlyForDeploymentPostprocessing = 0;
@@ -774,6 +781,7 @@
774781
CD16032A1AC02480000CD69A /* ObjectMapperTests.swift in Sources */,
775782
CD1603271AC02480000CD69A /* BasicTypesTestsToJSON.swift in Sources */,
776783
6A412A251BB0DA26001C3F67 /* PerformanceTests.swift in Sources */,
784+
DC99C8CD1CA261AD005C788C /* NullableKeysFromJSONTests.swift in Sources */,
777785
CD1603281AC02480000CD69A /* CustomTransformTests.swift in Sources */,
778786
);
779787
runOnlyForDeploymentPostprocessing = 0;

ObjectMapper/Core/FromJSON.swift

Lines changed: 21 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -34,21 +34,17 @@ internal final class FromJSON {
3434
field = value
3535
}
3636
}
37-
37+
3838
/// optional basic type
3939
class func optionalBasicType<FieldType>(inout field: FieldType?, object: FieldType?) {
40-
if let value = object {
41-
field = value
42-
}
40+
field = object
4341
}
44-
42+
4543
/// Implicitly unwrapped optional basic type
4644
class func optionalBasicType<FieldType>(inout field: FieldType!, object: FieldType?) {
47-
if let value = object {
48-
field = value
49-
}
45+
field = object
5046
}
51-
47+
5248
/// Mappable object
5349
class func object<N: Mappable>(inout field: N, map: Map) {
5450
if map.toObject {
@@ -57,43 +53,47 @@ internal final class FromJSON {
5753
field = value
5854
}
5955
}
60-
56+
6157
/// Optional Mappable Object
6258
class func optionalObject<N: Mappable>(inout field: N?, map: Map) {
63-
if let field = field where map.toObject {
59+
if let field = field where map.toObject && map.currentValue != nil {
6460
Mapper().map(map.currentValue, toObject: field)
6561
} else {
6662
field = Mapper().map(map.currentValue)
6763
}
6864
}
69-
65+
7066
/// Implicitly unwrapped Optional Mappable Object
7167
class func optionalObject<N: Mappable>(inout field: N!, map: Map) {
72-
if let field = field where map.toObject {
68+
if let field = field where map.toObject && map.currentValue != nil {
7369
Mapper().map(map.currentValue, toObject: field)
7470
} else {
7571
field = Mapper().map(map.currentValue)
7672
}
7773
}
78-
74+
7975
/// mappable object array
8076
class func objectArray<N: Mappable>(inout field: Array<N>, map: Map) {
8177
if let objects = Mapper<N>().mapArray(map.currentValue) {
8278
field = objects
8379
}
8480
}
85-
81+
8682
/// optional mappable object array
8783
class func optionalObjectArray<N: Mappable>(inout field: Array<N>?, map: Map) {
8884
if let objects: Array<N> = Mapper().mapArray(map.currentValue) {
8985
field = objects
86+
} else {
87+
field = nil
9088
}
9189
}
92-
90+
9391
/// Implicitly unwrapped optional mappable object array
9492
class func optionalObjectArray<N: Mappable>(inout field: Array<N>!, map: Map) {
9593
if let objects: Array<N> = Mapper().mapArray(map.currentValue) {
9694
field = objects
95+
} else {
96+
field = nil
9797
}
9898
}
9999

@@ -121,22 +121,22 @@ internal final class FromJSON {
121121
} else {
122122
if let objects = Mapper<N>().mapDictionary(map.currentValue) {
123123
field = objects
124-
}
124+
}
125125
}
126126
}
127-
127+
128128
/// Optional dictionary containing Mappable objects
129129
class func optionalObjectDictionary<N: Mappable>(inout field: Dictionary<String, N>?, map: Map) {
130-
if let field = field where map.toObject {
130+
if let field = field where map.toObject && map.currentValue != nil {
131131
Mapper().mapDictionary(map.currentValue, toDictionary: field)
132132
} else {
133133
field = Mapper().mapDictionary(map.currentValue)
134134
}
135135
}
136-
136+
137137
/// Implicitly unwrapped Dictionary containing Mappable objects
138138
class func optionalObjectDictionary<N: Mappable>(inout field: Dictionary<String, N>!, map: Map) {
139-
if let field = field where map.toObject {
139+
if let field = field where map.toObject && map.currentValue != nil {
140140
Mapper().mapDictionary(map.currentValue, toDictionary: field)
141141
} else {
142142
field = Mapper().mapDictionary(map.currentValue)
@@ -159,7 +159,6 @@ internal final class FromJSON {
159159
class func optionalObjectDictionaryOfArrays<N: Mappable>(inout field: Dictionary<String, [N]>!, map: Map) {
160160
field = Mapper<N>().mapDictionaryOfArrays(map.currentValue)
161161
}
162-
163162

164163
/// mappable object Set
165164
class func objectSet<N: Mappable>(inout field: Set<N>, map: Map) {

ObjectMapper/Core/Map.swift

Lines changed: 31 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,11 @@ public final class Map {
3434
public let mappingType: MappingType
3535

3636
public internal(set) var JSONDictionary: [String : AnyObject] = [:]
37+
public internal(set) var isKeyPresent = false
3738
public var currentValue: AnyObject?
3839
var currentKey: String?
3940
var keyIsNested = false
40-
41+
4142
let toObject: Bool // indicates whether the mapping is being applied to an existing object
4243

4344
/// Counter for failing cases of deserializing values to `let` properties.
@@ -61,13 +62,16 @@ public final class Map {
6162
// save key and value associated to it
6263
currentKey = key
6364
keyIsNested = nested
64-
65-
// check if a value exists for the current key
65+
66+
// check if a value exists for the current key
67+
// do this pre-check for performance reasons
6668
if nested == false {
67-
currentValue = JSONDictionary[key]
69+
let object = JSONDictionary[key], isNSNull = object is NSNull
70+
isKeyPresent = isNSNull ? true : object != nil
71+
currentValue = isNSNull ? nil : object
6872
} else {
6973
// break down the components of the key that are separated by .
70-
currentValue = valueFor(ArraySlice(key.componentsSeparatedByString(".")), dictionary: JSONDictionary)
74+
(isKeyPresent, currentValue) = valueFor(ArraySlice(key.componentsSeparatedByString(".")), dictionary: JSONDictionary)
7175
}
7276

7377
return self
@@ -106,56 +110,56 @@ public final class Map {
106110
}
107111

108112
/// Fetch value from JSON dictionary, loop through keyPathComponents until we reach the desired object
109-
private func valueFor(keyPathComponents: ArraySlice<String>, dictionary: [String: AnyObject]) -> AnyObject? {
113+
private func valueFor(keyPathComponents: ArraySlice<String>, dictionary: [String: AnyObject]) -> (Bool, AnyObject?) {
110114
// Implement it as a tail recursive function.
111115
if keyPathComponents.isEmpty {
112-
return nil
116+
return (false, nil)
113117
}
114118

115119
if let keyPath = keyPathComponents.first {
116120
let object = dictionary[keyPath]
117121
if object is NSNull {
118-
return nil
122+
return (true, nil)
119123
} else if let dict = object as? [String : AnyObject] where keyPathComponents.count > 1 {
120124
let tail = keyPathComponents.dropFirst()
121125
return valueFor(tail, dictionary: dict)
122126
} else if let array = object as? [AnyObject] where keyPathComponents.count > 1 {
123127
let tail = keyPathComponents.dropFirst()
124128
return valueFor(tail, array: array)
125129
} else {
126-
return object
130+
return (object != nil, object)
127131
}
128132
}
129133

130-
return nil
134+
return (false, nil)
131135
}
132136

133137
/// Fetch value from JSON Array, loop through keyPathComponents them until we reach the desired object
134-
private func valueFor(keyPathComponents: ArraySlice<String>, array: [AnyObject]) -> AnyObject? {
138+
private func valueFor(keyPathComponents: ArraySlice<String>, array: [AnyObject]) -> (Bool, AnyObject?) {
135139
// Implement it as a tail recursive function.
136140

137141
if keyPathComponents.isEmpty {
138-
return nil
142+
return (false, nil)
139143
}
140144

141145
//Try to convert keypath to Int as index
142146
if let keyPath = keyPathComponents.first,
143147
let index = Int(keyPath) where index >= 0 && index < array.count {
144-
145-
let object = array[index]
146-
147-
if object is NSNull {
148-
return nil
149-
} else if let array = object as? [AnyObject] where keyPathComponents.count > 1 {
150-
let tail = keyPathComponents.dropFirst()
151-
return valueFor(tail, array: array)
152-
} else if let dict = object as? [String : AnyObject] where keyPathComponents.count > 1 {
153-
let tail = keyPathComponents.dropFirst()
154-
return valueFor(tail, dictionary: dict)
155-
} else {
156-
return object
157-
}
148+
149+
let object = array[index]
150+
151+
if object is NSNull {
152+
return (true, nil)
153+
} else if let array = object as? [AnyObject] where keyPathComponents.count > 1 {
154+
let tail = keyPathComponents.dropFirst()
155+
return valueFor(tail, array: array)
156+
} else if let dict = object as? [String : AnyObject] where keyPathComponents.count > 1 {
157+
let tail = keyPathComponents.dropFirst()
158+
return valueFor(tail, dictionary: dict)
159+
} else {
160+
return (true, object)
161+
}
158162
}
159163

160-
return nil
164+
return (false, nil)
161165
}

0 commit comments

Comments
 (0)