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.

493 lines
16 KiB

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