You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

204 lines
7.1 KiB

  1. //
  2. // Map.swift
  3. // ObjectMapper
  4. //
  5. // Created by Tristan Himmelman on 2015-10-09.
  6. //
  7. // The MIT License (MIT)
  8. //
  9. // Copyright (c) 2014-2016 Hearst
  10. //
  11. // Permission is hereby granted, free of charge, to any person obtaining a copy
  12. // of this software and associated documentation files (the "Software"), to deal
  13. // in the Software without restriction, including without limitation the rights
  14. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  15. // copies of the Software, and to permit persons to whom the Software is
  16. // furnished to do so, subject to the following conditions:
  17. //
  18. // The above copyright notice and this permission notice shall be included in
  19. // all copies or substantial portions of the Software.
  20. //
  21. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  22. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  23. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  24. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  25. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  26. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  27. // THE SOFTWARE.
  28. import Foundation
  29. /// MapContext is available for developers who wish to pass information around during the mapping process.
  30. public protocol MapContext {
  31. }
  32. /// A class used for holding mapping data
  33. public final class Map {
  34. public let mappingType: MappingType
  35. public internal(set) var JSON: [String: Any] = [:]
  36. public internal(set) var isKeyPresent = false
  37. public internal(set) var currentValue: Any?
  38. public internal(set) var currentKey: String?
  39. var keyIsNested = false
  40. public internal(set) var nestedKeyDelimiter: String = "."
  41. public var context: MapContext?
  42. public var shouldIncludeNilValues = false /// If this is set to true, toJSON output will include null values for any variables that are not set.
  43. public let toObject: Bool // indicates whether the mapping is being applied to an existing object
  44. public init(mappingType: MappingType, JSON: [String: Any], toObject: Bool = false, context: MapContext? = nil, shouldIncludeNilValues: Bool = false) {
  45. self.mappingType = mappingType
  46. self.JSON = JSON
  47. self.toObject = toObject
  48. self.context = context
  49. self.shouldIncludeNilValues = shouldIncludeNilValues
  50. }
  51. /// Sets the current mapper value and key.
  52. /// The Key paramater can be a period separated string (ex. "distance.value") to access sub objects.
  53. public subscript(key: String) -> Map {
  54. // save key and value associated to it
  55. return self.subscript(key: key)
  56. }
  57. public subscript(key: String, delimiter delimiter: String) -> Map {
  58. return self.subscript(key: key, delimiter: delimiter)
  59. }
  60. public subscript(key: String, nested nested: Bool) -> Map {
  61. return self.subscript(key: key, nested: nested)
  62. }
  63. public subscript(key: String, nested nested: Bool, delimiter delimiter: String) -> Map {
  64. return self.subscript(key: key, nested: nested, delimiter: delimiter)
  65. }
  66. public subscript(key: String, ignoreNil ignoreNil: Bool) -> Map {
  67. return self.subscript(key: key, ignoreNil: ignoreNil)
  68. }
  69. public subscript(key: String, delimiter delimiter: String, ignoreNil ignoreNil: Bool) -> Map {
  70. return self.subscript(key: key, delimiter: delimiter, ignoreNil: ignoreNil)
  71. }
  72. public subscript(key: String, nested nested: Bool, ignoreNil ignoreNil: Bool) -> Map {
  73. return self.subscript(key: key, nested: nested, ignoreNil: ignoreNil)
  74. }
  75. public subscript(key: String, nested nested: Bool?, delimiter delimiter: String, ignoreNil ignoreNil: Bool) -> Map {
  76. return self.subscript(key: key, nested: nested, delimiter: delimiter, ignoreNil: ignoreNil)
  77. }
  78. private func `subscript`(key: String, nested: Bool? = nil, delimiter: String = ".", ignoreNil: Bool = false) -> Map {
  79. // save key and value associated to it
  80. currentKey = key
  81. keyIsNested = nested ?? key.contains(delimiter)
  82. nestedKeyDelimiter = delimiter
  83. if mappingType == .fromJSON {
  84. // check if a value exists for the current key
  85. // do this pre-check for performance reasons
  86. if keyIsNested {
  87. // break down the components of the key that are separated by delimiter
  88. (isKeyPresent, currentValue) = valueFor(ArraySlice(key.components(separatedBy: delimiter)), dictionary: JSON)
  89. } else {
  90. let object = JSON[key]
  91. let isNSNull = object is NSNull
  92. isKeyPresent = isNSNull ? true : object != nil
  93. currentValue = isNSNull ? nil : object
  94. }
  95. // update isKeyPresent if ignoreNil is true
  96. if ignoreNil && currentValue == nil {
  97. isKeyPresent = false
  98. }
  99. }
  100. return self
  101. }
  102. public func value<T>() -> T? {
  103. let value = currentValue as? T
  104. // Swift 4.1 breaks Float casting from `NSNumber`. So Added extra checks for `Float` `[Float]` and `[String:Float]`
  105. if value == nil && T.self == Float.self {
  106. if let v = currentValue as? NSNumber {
  107. return v.floatValue as? T
  108. }
  109. } else if value == nil && T.self == [Float].self {
  110. if let v = currentValue as? [Double] {
  111. #if swift(>=4.1)
  112. return v.compactMap{ Float($0) } as? T
  113. #else
  114. return v.flatMap{ Float($0) } as? T
  115. #endif
  116. }
  117. } else if value == nil && T.self == [String:Float].self {
  118. if let v = currentValue as? [String:Double] {
  119. return v.mapValues{ Float($0) } as? T
  120. }
  121. }
  122. return value
  123. }
  124. }
  125. /// Fetch value from JSON dictionary, loop through keyPathComponents until we reach the desired object
  126. private func valueFor(_ keyPathComponents: ArraySlice<String>, dictionary: [String: Any]) -> (Bool, Any?) {
  127. // Implement it as a tail recursive function.
  128. if keyPathComponents.isEmpty {
  129. return (false, nil)
  130. }
  131. if let keyPath = keyPathComponents.first {
  132. let isTail = keyPathComponents.count == 1
  133. let object = dictionary[keyPath]
  134. if object is NSNull {
  135. return (isTail, nil)
  136. } else if keyPathComponents.count > 1, let dict = object as? [String: Any] {
  137. let tail = keyPathComponents.dropFirst()
  138. return valueFor(tail, dictionary: dict)
  139. } else if keyPathComponents.count > 1, let array = object as? [Any] {
  140. let tail = keyPathComponents.dropFirst()
  141. return valueFor(tail, array: array)
  142. } else {
  143. return (isTail && object != nil, object)
  144. }
  145. }
  146. return (false, nil)
  147. }
  148. /// Fetch value from JSON Array, loop through keyPathComponents them until we reach the desired object
  149. private func valueFor(_ keyPathComponents: ArraySlice<String>, array: [Any]) -> (Bool, Any?) {
  150. // Implement it as a tail recursive function.
  151. if keyPathComponents.isEmpty {
  152. return (false, nil)
  153. }
  154. //Try to convert keypath to Int as index
  155. if let keyPath = keyPathComponents.first,
  156. let index = Int(keyPath) , index >= 0 && index < array.count {
  157. let isTail = keyPathComponents.count == 1
  158. let object = array[index]
  159. if object is NSNull {
  160. return (isTail, nil)
  161. } else if keyPathComponents.count > 1, let array = object as? [Any] {
  162. let tail = keyPathComponents.dropFirst()
  163. return valueFor(tail, array: array)
  164. } else if keyPathComponents.count > 1, let dict = object as? [String: Any] {
  165. let tail = keyPathComponents.dropFirst()
  166. return valueFor(tail, dictionary: dict)
  167. } else {
  168. return (isTail, object)
  169. }
  170. }
  171. return (false, nil)
  172. }