Skip to content

Commit

Permalink
Merge pull request #403 from pixel4/add-nullable-from-json
Browse files Browse the repository at this point in the history
Add the ability to reset object properties to `nil` from JSON
  • Loading branch information
tristanhimmelman committed Mar 23, 2016
2 parents 9e0122e + 70720a7 commit f72ad15
Show file tree
Hide file tree
Showing 5 changed files with 291 additions and 124 deletions.
8 changes: 8 additions & 0 deletions ObjectMapper.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -196,6 +199,7 @@
CD50B6FC1A82518300744312 /* TransformType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransformType.swift; sourceTree = "<group>"; };
CD71C8C01A7218AD009D4161 /* TransformOf.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = TransformOf.swift; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
D86BDEA31A51E5AC00120819 /* ISO8601DateTransform.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ISO8601DateTransform.swift; sourceTree = "<group>"; };
DC99C8CB1CA261A8005C788C /* NullableKeysFromJSONTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NullableKeysFromJSONTests.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
43 changes: 21 additions & 22 deletions ObjectMapper/Core/FromJSON.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,21 +34,17 @@ internal final class FromJSON {
field = value
}
}

/// optional basic type
class func optionalBasicType<FieldType>(inout field: FieldType?, object: FieldType?) {
if let value = object {
field = value
}
field = object
}

/// Implicitly unwrapped optional basic type
class func optionalBasicType<FieldType>(inout field: FieldType!, object: FieldType?) {
if let value = object {
field = value
}
field = object
}

/// Mappable object
class func object<N: Mappable>(inout field: N, map: Map) {
if map.toObject {
Expand All @@ -57,43 +53,47 @@ internal final class FromJSON {
field = value
}
}

/// Optional Mappable Object
class func optionalObject<N: Mappable>(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<N: Mappable>(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<N: Mappable>(inout field: Array<N>, map: Map) {
if let objects = Mapper<N>().mapArray(map.currentValue) {
field = objects
}
}

/// optional mappable object array
class func optionalObjectArray<N: Mappable>(inout field: Array<N>?, map: Map) {
if let objects: Array<N> = Mapper().mapArray(map.currentValue) {
field = objects
} else {
field = nil
}
}

/// Implicitly unwrapped optional mappable object array
class func optionalObjectArray<N: Mappable>(inout field: Array<N>!, map: Map) {
if let objects: Array<N> = Mapper().mapArray(map.currentValue) {
field = objects
} else {
field = nil
}
}

Expand Down Expand Up @@ -121,22 +121,22 @@ internal final class FromJSON {
} else {
if let objects = Mapper<N>().mapDictionary(map.currentValue) {
field = objects
}
}
}
}

/// Optional dictionary containing Mappable objects
class func optionalObjectDictionary<N: Mappable>(inout field: Dictionary<String, N>?, 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<N: Mappable>(inout field: Dictionary<String, N>!, 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)
Expand All @@ -159,7 +159,6 @@ internal final class FromJSON {
class func optionalObjectDictionaryOfArrays<N: Mappable>(inout field: Dictionary<String, [N]>!, map: Map) {
field = Mapper<N>().mapDictionaryOfArrays(map.currentValue)
}


/// mappable object Set
class func objectSet<N: Mappable>(inout field: Set<N>, map: Map) {
Expand Down
58 changes: 31 additions & 27 deletions ObjectMapper/Core/Map.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -61,13 +62,16 @@ public final class Map {
// save key and value associated to it
currentKey = key
keyIsNested = nested

// check if a value exists for the current key

// check if a value exists for the current key
// do this pre-check for performance reasons
if nested == false {
currentValue = JSONDictionary[key]
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 .
currentValue = valueFor(ArraySlice(key.componentsSeparatedByString(".")), dictionary: JSONDictionary)
(isKeyPresent, currentValue) = valueFor(ArraySlice(key.componentsSeparatedByString(".")), dictionary: JSONDictionary)
}

return self
Expand Down Expand Up @@ -106,56 +110,56 @@ public final class Map {
}

/// Fetch value from JSON dictionary, loop through keyPathComponents until we reach the desired object
private func valueFor(keyPathComponents: ArraySlice<String>, dictionary: [String: AnyObject]) -> AnyObject? {
private func valueFor(keyPathComponents: ArraySlice<String>, 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)
} else if let array = object as? [AnyObject] where keyPathComponents.count > 1 {
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<String>, array: [AnyObject]) -> AnyObject? {
private func valueFor(keyPathComponents: ArraySlice<String>, 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)
}
Loading

0 comments on commit f72ad15

Please sign in to comment.