Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the ability to reset object properties to nil from JSON #403

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check is important for performance reasons. We previously didn't include it and the valueFor function became a performance bottleneck because it was being called so often without any need.

Can you please revert this

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

absolutely.. I'll revert it and check my tests tonight.

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