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.

490 lines
15 KiB

  1. //
  2. // Mapper.swift
  3. // ObjectMapper
  4. //
  5. // Created by Tristan Himmelman on 2014-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. public enum MappingType {
  30. case fromJSON
  31. case toJSON
  32. }
  33. /// The Mapper class provides methods for converting Model objects to JSON and methods for converting JSON to Model objects
  34. public final class Mapper<N: BaseMappable> {
  35. public var context: MapContext?
  36. public var shouldIncludeNilValues = false /// If this is set to true, toJSON output will include null values for any variables that are not set.
  37. public init(context: MapContext? = nil, shouldIncludeNilValues: Bool = false){
  38. self.context = context
  39. self.shouldIncludeNilValues = shouldIncludeNilValues
  40. }
  41. // MARK: Mapping functions that map to an existing object toObject
  42. /// Maps a JSON object to an existing Mappable object if it is a JSON dictionary, or returns the passed object as is
  43. public func map(JSONObject: Any?, toObject object: N) -> N {
  44. if let JSON = JSONObject as? [String: Any] {
  45. return map(JSON: JSON, toObject: object)
  46. }
  47. return object
  48. }
  49. /// Map a JSON string onto an existing object
  50. public func map(JSONString: String, toObject object: N) -> N {
  51. if let JSON = Mapper.parseJSONStringIntoDictionary(JSONString: JSONString) {
  52. return map(JSON: JSON, toObject: object)
  53. }
  54. return object
  55. }
  56. /// Maps a JSON dictionary to an existing object that conforms to Mappable.
  57. /// Usefull for those pesky objects that have crappy designated initializers like NSManagedObject
  58. public func map(JSON: [String: Any], toObject object: N) -> N {
  59. var mutableObject = object
  60. let map = Map(mappingType: .fromJSON, JSON: JSON, toObject: true, context: context, shouldIncludeNilValues: shouldIncludeNilValues)
  61. mutableObject.mapping(map: map)
  62. return mutableObject
  63. }
  64. //MARK: Mapping functions that create an object
  65. /// Map a JSON string to an object that conforms to Mappable
  66. public func map(JSONString: String) -> N? {
  67. if let JSON = Mapper.parseJSONStringIntoDictionary(JSONString: JSONString) {
  68. return map(JSON: JSON)
  69. }
  70. return nil
  71. }
  72. /// Maps a JSON object to a Mappable object if it is a JSON dictionary or NSString, or returns nil.
  73. public func map(JSONObject: Any?) -> N? {
  74. if let JSON = JSONObject as? [String: Any] {
  75. return map(JSON: JSON)
  76. }
  77. return nil
  78. }
  79. /// Maps a JSON dictionary to an object that conforms to Mappable
  80. public func map(JSON: [String: Any]) -> N? {
  81. let map = Map(mappingType: .fromJSON, JSON: JSON, context: context, shouldIncludeNilValues: shouldIncludeNilValues)
  82. if let klass = N.self as? StaticMappable.Type { // Check if object is StaticMappable
  83. if var object = klass.objectForMapping(map: map) as? N {
  84. object.mapping(map: map)
  85. return object
  86. }
  87. } else if let klass = N.self as? Mappable.Type { // Check if object is Mappable
  88. if var object = klass.init(map: map) as? N {
  89. object.mapping(map: map)
  90. return object
  91. }
  92. } else if let klass = N.self as? ImmutableMappable.Type { // Check if object is ImmutableMappable
  93. do {
  94. return try klass.init(map: map) as? N
  95. } catch let error {
  96. #if DEBUG
  97. let exception: NSException
  98. if let mapError = error as? MapError {
  99. exception = NSException(name: .init(rawValue: "MapError"), reason: mapError.description, userInfo: nil)
  100. } else {
  101. exception = NSException(name: .init(rawValue: "ImmutableMappableError"), reason: error.localizedDescription, userInfo: nil)
  102. }
  103. exception.raise()
  104. #endif
  105. }
  106. } else {
  107. // Ensure BaseMappable is not implemented directly
  108. assert(false, "BaseMappable should not be implemented directly. Please implement Mappable, StaticMappable or ImmutableMappable")
  109. }
  110. return nil
  111. }
  112. // MARK: Mapping functions for Arrays and Dictionaries
  113. /// Maps a JSON array to an object that conforms to Mappable
  114. public func mapArray(JSONString: String) -> [N]? {
  115. let parsedJSON: Any? = Mapper.parseJSONString(JSONString: JSONString)
  116. if let objectArray = mapArray(JSONObject: parsedJSON) {
  117. return objectArray
  118. }
  119. // failed to parse JSON into array form
  120. // try to parse it into a dictionary and then wrap it in an array
  121. if let object = map(JSONObject: parsedJSON) {
  122. return [object]
  123. }
  124. return nil
  125. }
  126. /// Maps a JSON object to an array of Mappable objects if it is an array of JSON dictionary, or returns nil.
  127. public func mapArray(JSONObject: Any?) -> [N]? {
  128. if let JSONArray = JSONObject as? [[String: Any]] {
  129. return mapArray(JSONArray: JSONArray)
  130. }
  131. return nil
  132. }
  133. /// Maps an array of JSON dictionary to an array of Mappable objects
  134. public func mapArray(JSONArray: [[String: Any]]) -> [N] {
  135. // map every element in JSON array to type N
  136. #if swift(>=4.1)
  137. let result = JSONArray.compactMap(map)
  138. #else
  139. let result = JSONArray.flatMap(map)
  140. #endif
  141. return result
  142. }
  143. /// Maps a JSON object to a dictionary of Mappable objects if it is a JSON dictionary of dictionaries, or returns nil.
  144. public func mapDictionary(JSONString: String) -> [String: N]? {
  145. let parsedJSON: Any? = Mapper.parseJSONString(JSONString: JSONString)
  146. return mapDictionary(JSONObject: parsedJSON)
  147. }
  148. /// Maps a JSON object to a dictionary of Mappable objects if it is a JSON dictionary of dictionaries, or returns nil.
  149. public func mapDictionary(JSONObject: Any?) -> [String: N]? {
  150. if let JSON = JSONObject as? [String: [String: Any]] {
  151. return mapDictionary(JSON: JSON)
  152. }
  153. return nil
  154. }
  155. /// Maps a JSON dictionary of dictionaries to a dictionary of Mappable objects
  156. public func mapDictionary(JSON: [String: [String: Any]]) -> [String: N]? {
  157. // map every value in dictionary to type N
  158. let result = JSON.filterMap(map)
  159. if result.isEmpty == false {
  160. return result
  161. }
  162. return nil
  163. }
  164. /// Maps a JSON object to a dictionary of Mappable objects if it is a JSON dictionary of dictionaries, or returns nil.
  165. public func mapDictionary(JSONObject: Any?, toDictionary dictionary: [String: N]) -> [String: N] {
  166. if let JSON = JSONObject as? [String : [String : Any]] {
  167. return mapDictionary(JSON: JSON, toDictionary: dictionary)
  168. }
  169. return dictionary
  170. }
  171. /// Maps a JSON dictionary of dictionaries to an existing dictionary of Mappable objects
  172. public func mapDictionary(JSON: [String: [String: Any]], toDictionary dictionary: [String: N]) -> [String: N] {
  173. var mutableDictionary = dictionary
  174. for (key, value) in JSON {
  175. if let object = dictionary[key] {
  176. _ = map(JSON: value, toObject: object)
  177. } else {
  178. mutableDictionary[key] = map(JSON: value)
  179. }
  180. }
  181. return mutableDictionary
  182. }
  183. /// Maps a JSON object to a dictionary of arrays of Mappable objects
  184. public func mapDictionaryOfArrays(JSONObject: Any?) -> [String: [N]]? {
  185. if let JSON = JSONObject as? [String: [[String: Any]]] {
  186. return mapDictionaryOfArrays(JSON: JSON)
  187. }
  188. return nil
  189. }
  190. ///Maps a JSON dictionary of arrays to a dictionary of arrays of Mappable objects
  191. public func mapDictionaryOfArrays(JSON: [String: [[String: Any]]]) -> [String: [N]]? {
  192. // map every value in dictionary to type N
  193. let result = JSON.filterMap {
  194. mapArray(JSONArray: $0)
  195. }
  196. if result.isEmpty == false {
  197. return result
  198. }
  199. return nil
  200. }
  201. /// Maps an 2 dimentional array of JSON dictionaries to a 2 dimentional array of Mappable objects
  202. public func mapArrayOfArrays(JSONObject: Any?) -> [[N]]? {
  203. if let JSONArray = JSONObject as? [[[String: Any]]] {
  204. var objectArray = [[N]]()
  205. for innerJSONArray in JSONArray {
  206. let array = mapArray(JSONArray: innerJSONArray)
  207. objectArray.append(array)
  208. }
  209. if objectArray.isEmpty == false {
  210. return objectArray
  211. }
  212. }
  213. return nil
  214. }
  215. // MARK: Utility functions for converting strings to JSON objects
  216. /// Convert a JSON String into a Dictionary<String, Any> using NSJSONSerialization
  217. public static func parseJSONStringIntoDictionary(JSONString: String) -> [String: Any]? {
  218. let parsedJSON: Any? = Mapper.parseJSONString(JSONString: JSONString)
  219. return parsedJSON as? [String: Any]
  220. }
  221. /// Convert a JSON String into an Object using NSJSONSerialization
  222. public static func parseJSONString(JSONString: String) -> Any? {
  223. let data = JSONString.data(using: String.Encoding.utf8, allowLossyConversion: true)
  224. if let data = data {
  225. let parsedJSON: Any?
  226. do {
  227. parsedJSON = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments)
  228. } catch let error {
  229. print(error)
  230. parsedJSON = nil
  231. }
  232. return parsedJSON
  233. }
  234. return nil
  235. }
  236. }
  237. extension Mapper {
  238. // MARK: Functions that create model from JSON file
  239. /// JSON file to Mappable object
  240. /// - parameter JSONfile: Filename
  241. /// - Returns: Mappable object
  242. public func map(JSONfile: String) -> N? {
  243. if let path = Bundle.main.path(forResource: JSONfile, ofType: nil) {
  244. do {
  245. let JSONString = try String(contentsOfFile: path)
  246. do {
  247. return self.map(JSONString: JSONString)
  248. }
  249. } catch {
  250. return nil
  251. }
  252. }
  253. return nil
  254. }
  255. /// JSON file to Mappable object array
  256. /// - parameter JSONfile: Filename
  257. /// - Returns: Mappable object array
  258. public func mapArray(JSONfile: String) -> [N]? {
  259. if let path = Bundle.main.path(forResource: JSONfile, ofType: nil) {
  260. do {
  261. let JSONString = try String(contentsOfFile: path)
  262. do {
  263. return self.mapArray(JSONString: JSONString)
  264. }
  265. } catch {
  266. return nil
  267. }
  268. }
  269. return nil
  270. }
  271. }
  272. extension Mapper {
  273. // MARK: Functions that create JSON from objects
  274. ///Maps an object that conforms to Mappable to a JSON dictionary <String, Any>
  275. public func toJSON(_ object: N) -> [String: Any] {
  276. var mutableObject = object
  277. let map = Map(mappingType: .toJSON, JSON: [:], context: context, shouldIncludeNilValues: shouldIncludeNilValues)
  278. mutableObject.mapping(map: map)
  279. return map.JSON
  280. }
  281. ///Maps an array of Objects to an array of JSON dictionaries [[String: Any]]
  282. public func toJSONArray(_ array: [N]) -> [[String: Any]] {
  283. return array.map {
  284. // convert every element in array to JSON dictionary equivalent
  285. self.toJSON($0)
  286. }
  287. }
  288. ///Maps a dictionary of Objects that conform to Mappable to a JSON dictionary of dictionaries.
  289. public func toJSONDictionary(_ dictionary: [String: N]) -> [String: [String: Any]] {
  290. return dictionary.map { (arg: (key: String, value: N)) in
  291. // convert every value in dictionary to its JSON dictionary equivalent
  292. return (arg.key, self.toJSON(arg.value))
  293. }
  294. }
  295. ///Maps a dictionary of Objects that conform to Mappable to a JSON dictionary of dictionaries.
  296. public func toJSONDictionaryOfArrays(_ dictionary: [String: [N]]) -> [String: [[String: Any]]] {
  297. return dictionary.map { (arg: (key: String, value: [N])) in
  298. // convert every value (array) in dictionary to its JSON dictionary equivalent
  299. return (arg.key, self.toJSONArray(arg.value))
  300. }
  301. }
  302. /// Maps an Object to a JSON string with option of pretty formatting
  303. public func toJSONString(_ object: N, prettyPrint: Bool = false) -> String? {
  304. let JSONDict = toJSON(object)
  305. return Mapper.toJSONString(JSONDict as Any, prettyPrint: prettyPrint)
  306. }
  307. /// Maps an array of Objects to a JSON string with option of pretty formatting
  308. public func toJSONString(_ array: [N], prettyPrint: Bool = false) -> String? {
  309. let JSONDict = toJSONArray(array)
  310. return Mapper.toJSONString(JSONDict as Any, prettyPrint: prettyPrint)
  311. }
  312. /// Converts an Object to a JSON string with option of pretty formatting
  313. public static func toJSONString(_ JSONObject: Any, prettyPrint: Bool) -> String? {
  314. let options: JSONSerialization.WritingOptions = prettyPrint ? .prettyPrinted : []
  315. if let JSON = Mapper.toJSONData(JSONObject, options: options) {
  316. return String(data: JSON, encoding: String.Encoding.utf8)
  317. }
  318. return nil
  319. }
  320. /// Converts an Object to JSON data with options
  321. public static func toJSONData(_ JSONObject: Any, options: JSONSerialization.WritingOptions) -> Data? {
  322. if JSONSerialization.isValidJSONObject(JSONObject) {
  323. let JSONData: Data?
  324. do {
  325. JSONData = try JSONSerialization.data(withJSONObject: JSONObject, options: options)
  326. } catch let error {
  327. print(error)
  328. JSONData = nil
  329. }
  330. return JSONData
  331. }
  332. return nil
  333. }
  334. }
  335. extension Mapper where N: Hashable {
  336. /// Maps a JSON array to an object that conforms to Mappable
  337. public func mapSet(JSONString: String) -> Set<N>? {
  338. let parsedJSON: Any? = Mapper.parseJSONString(JSONString: JSONString)
  339. if let objectArray = mapArray(JSONObject: parsedJSON) {
  340. return Set(objectArray)
  341. }
  342. // failed to parse JSON into array form
  343. // try to parse it into a dictionary and then wrap it in an array
  344. if let object = map(JSONObject: parsedJSON) {
  345. return Set([object])
  346. }
  347. return nil
  348. }
  349. /// Maps a JSON object to an Set of Mappable objects if it is an array of JSON dictionary, or returns nil.
  350. public func mapSet(JSONObject: Any?) -> Set<N>? {
  351. if let JSONArray = JSONObject as? [[String: Any]] {
  352. return mapSet(JSONArray: JSONArray)
  353. }
  354. return nil
  355. }
  356. /// Maps an Set of JSON dictionary to an array of Mappable objects
  357. public func mapSet(JSONArray: [[String: Any]]) -> Set<N> {
  358. // map every element in JSON array to type N
  359. #if swift(>=4.1)
  360. return Set(JSONArray.compactMap(map))
  361. #else
  362. return Set(JSONArray.flatMap(map))
  363. #endif
  364. }
  365. ///Maps a Set of Objects to a Set of JSON dictionaries [[String : Any]]
  366. public func toJSONSet(_ set: Set<N>) -> [[String: Any]] {
  367. return set.map {
  368. // convert every element in set to JSON dictionary equivalent
  369. self.toJSON($0)
  370. }
  371. }
  372. /// Maps a set of Objects to a JSON string with option of pretty formatting
  373. public func toJSONString(_ set: Set<N>, prettyPrint: Bool = false) -> String? {
  374. let JSONDict = toJSONSet(set)
  375. return Mapper.toJSONString(JSONDict as Any, prettyPrint: prettyPrint)
  376. }
  377. }
  378. extension Dictionary {
  379. internal func map<K, V>(_ f: (Element) throws -> (K, V)) rethrows -> [K: V] {
  380. var mapped = [K: V]()
  381. for element in self {
  382. let newElement = try f(element)
  383. mapped[newElement.0] = newElement.1
  384. }
  385. return mapped
  386. }
  387. internal func map<K, V>(_ f: (Element) throws -> (K, [V])) rethrows -> [K: [V]] {
  388. var mapped = [K: [V]]()
  389. for element in self {
  390. let newElement = try f(element)
  391. mapped[newElement.0] = newElement.1
  392. }
  393. return mapped
  394. }
  395. internal func filterMap<U>(_ f: (Value) throws -> U?) rethrows -> [Key: U] {
  396. var mapped = [Key: U]()
  397. for (key, value) in self {
  398. if let newValue = try f(value) {
  399. mapped[key] = newValue
  400. }
  401. }
  402. return mapped
  403. }
  404. }