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.

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