diff --git a/ObjectMapper.podspec b/ObjectMapper.podspec index abdd01f0..fee08a62 100644 --- a/ObjectMapper.podspec +++ b/ObjectMapper.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'ObjectMapper' - s.version = '2.2.1' + s.version = '2.2.2' s.license = 'MIT' s.summary = 'JSON Object mapping written in Swift' s.homepage = 'https://github.com/Hearst-DD/ObjectMapper' diff --git a/Sources/ImmutableMappable.swift b/Sources/ImmutableMappable.swift index 4e2ba540..ae991794 100644 --- a/Sources/ImmutableMappable.swift +++ b/Sources/ImmutableMappable.swift @@ -242,7 +242,7 @@ public extension Mapper where N: ImmutableMappable { internal extension Mapper where N: BaseMappable { internal func mapOrFail(JSON: [String: Any]) throws -> N { - let map = Map(mappingType: .fromJSON, JSON: JSON, context: context) + let map = Map(mappingType: .fromJSON, JSON: JSON, context: context, shouldIncludeNilValues: shouldIncludeNilValues) // Check if object is ImmutableMappable, if so use ImmutableMappable protocol for mapping if let klass = N.self as? ImmutableMappable.Type, diff --git a/Sources/Map.swift b/Sources/Map.swift index a69440ea..22f87b5a 100644 --- a/Sources/Map.swift +++ b/Sources/Map.swift @@ -45,14 +45,17 @@ public final class Map { var keyIsNested = false public internal(set) var nestedKeyDelimiter: String = "." public var context: MapContext? + public var shouldIncludeNilValues = false /// If this is set to true, toJSON output will include null values for any variables that are not set. let toObject: Bool // indicates whether the mapping is being applied to an existing object - public init(mappingType: MappingType, JSON: [String: Any], toObject: Bool = false, context: MapContext? = nil) { + public init(mappingType: MappingType, JSON: [String: Any], toObject: Bool = false, context: MapContext? = nil, shouldIncludeNilValues: Bool = false) { + self.mappingType = mappingType self.JSON = JSON self.toObject = toObject self.context = context + self.shouldIncludeNilValues = shouldIncludeNilValues } /// Sets the current mapper value and key. @@ -133,10 +136,10 @@ private func valueFor(_ keyPathComponents: ArraySlice, dictionary: [Stri let object = dictionary[keyPath] if object is NSNull { return (true, nil) - } else if let dict = object as? [String: Any] , keyPathComponents.count > 1 { + } else if keyPathComponents.count > 1, let dict = object as? [String: Any] { let tail = keyPathComponents.dropFirst() return valueFor(tail, dictionary: dict) - } else if let array = object as? [Any] , keyPathComponents.count > 1 { + } else if keyPathComponents.count > 1, let array = object as? [Any] { let tail = keyPathComponents.dropFirst() return valueFor(tail, array: array) } else { @@ -159,19 +162,19 @@ private func valueFor(_ keyPathComponents: ArraySlice, array: [Any]) -> if let keyPath = keyPathComponents.first, let index = Int(keyPath) , index >= 0 && index < array.count { - let object = array[index] - - if object is NSNull { - return (true, nil) - } else if let array = object as? [Any] , keyPathComponents.count > 1 { - let tail = keyPathComponents.dropFirst() - return valueFor(tail, array: array) - } else if let dict = object as? [String: Any] , keyPathComponents.count > 1 { - let tail = keyPathComponents.dropFirst() - return valueFor(tail, dictionary: dict) - } else { - return (true, object) - } + let object = array[index] + + if object is NSNull { + return (true, nil) + } else if keyPathComponents.count > 1, let array = object as? [Any] { + let tail = keyPathComponents.dropFirst() + return valueFor(tail, array: array) + } else if keyPathComponents.count > 1, let dict = object as? [String: Any] { + let tail = keyPathComponents.dropFirst() + return valueFor(tail, dictionary: dict) + } else { + return (true, object) + } } return (false, nil) diff --git a/Sources/Mapper.swift b/Sources/Mapper.swift index d84cc155..9071acc0 100755 --- a/Sources/Mapper.swift +++ b/Sources/Mapper.swift @@ -37,9 +37,11 @@ public enum MappingType { public final class Mapper { public var context: MapContext? + public var shouldIncludeNilValues = false /// If this is set to true, toJSON output will include null values for any variables that are not set. - public init(context: MapContext? = nil){ + public init(context: MapContext? = nil, shouldIncludeNilValues: Bool = false){ self.context = context + self.shouldIncludeNilValues = shouldIncludeNilValues } // MARK: Mapping functions that map to an existing object toObject @@ -65,7 +67,7 @@ public final class Mapper { /// Usefull for those pesky objects that have crappy designated initializers like NSManagedObject public func map(JSON: [String: Any], toObject object: N) -> N { var mutableObject = object - let map = Map(mappingType: .fromJSON, JSON: JSON, toObject: true, context: context) + let map = Map(mappingType: .fromJSON, JSON: JSON, toObject: true, context: context, shouldIncludeNilValues: shouldIncludeNilValues) mutableObject.mapping(map: map) return mutableObject } @@ -92,7 +94,7 @@ public final class Mapper { /// Maps a JSON dictionary to an object that conforms to Mappable public func map(JSON: [String: Any]) -> N? { - let map = Map(mappingType: .fromJSON, JSON: JSON, context: context) + let map = Map(mappingType: .fromJSON, JSON: JSON, context: context, shouldIncludeNilValues: shouldIncludeNilValues) if let klass = N.self as? StaticMappable.Type { // Check if object is StaticMappable if var object = klass.objectForMapping(map: map) as? N { @@ -272,7 +274,7 @@ extension Mapper { ///Maps an object that conforms to Mappable to a JSON dictionary public func toJSON(_ object: N) -> [String: Any] { var mutableObject = object - let map = Map(mappingType: .toJSON, JSON: [:], context: context) + let map = Map(mappingType: .toJSON, JSON: [:], context: context, shouldIncludeNilValues: shouldIncludeNilValues) mutableObject.mapping(map: map) return map.JSON } diff --git a/Sources/ToJSON.swift b/Sources/ToJSON.swift index 4d595325..b1eff4b7 100644 --- a/Sources/ToJSON.swift +++ b/Sources/ToJSON.swift @@ -73,6 +73,7 @@ internal final class ToJSON { || x is Double || x is Float || x is String + || x is NSNull || x is Array // Arrays || x is Array || x is Array @@ -92,10 +93,12 @@ internal final class ToJSON { setValue(x, map: map) } } - + class func optionalBasicType(_ field: N?, map: Map) { if let field = field { basicType(field, map: map) + } else if map.shouldIncludeNilValues { + basicType(NSNull(), map: map) //If BasicType is nil, emil NSNull into the JSON output } } @@ -155,13 +158,13 @@ internal final class ToJSON { setValue(JSONObjects, map: map) } - + class func optionalObjectDictionary(_ field: Dictionary?, map: Map) { - if let field = field { + if let field = field { objectDictionary(field, map: map) - } - } - + } + } + class func objectDictionaryOfArrays(_ field: Dictionary, map: Map) { let JSONObjects = Mapper(context: map.context).toJSONDictionaryOfArrays(field) diff --git a/Tests/ObjectMapperTests/BasicTypesTestsToJSON.swift b/Tests/ObjectMapperTests/BasicTypesTestsToJSON.swift index 703d876f..55ca4954 100644 --- a/Tests/ObjectMapperTests/BasicTypesTestsToJSON.swift +++ b/Tests/ObjectMapperTests/BasicTypesTestsToJSON.swift @@ -46,6 +46,18 @@ class BasicTypesTestsToJSON: XCTestCase { // MARK: Test mapping to JSON and back (basic types: Bool, Int, Double, Float, String) + func testShouldIncludeNilValues(){ + let object = BasicTypes() + + let JSONWithNil = Mapper(shouldIncludeNilValues: true).toJSONString(object, prettyPrint: true) + let JSONWithoutNil = Mapper(shouldIncludeNilValues: false).toJSONString(object, prettyPrint: true) + + //TODO This test could be improved + XCTAssertNotNil(JSONWithNil) + XCTAssertTrue((JSONWithNil!.characters.count) > 5) + XCTAssertTrue((JSONWithNil!.characters.count) != (JSONWithoutNil!.characters.count)) + } + func testMappingBoolToJSON(){ let value: Bool = true let object = BasicTypes()