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
493 lines
16 KiB
// |
|
// Mapper.swift |
|
// ObjectMapper |
|
// |
|
// Created by Tristan Himmelman on 2014-10-09. |
|
// |
|
// The MIT License (MIT) |
|
// |
|
// Copyright (c) 2014-2018 Tristan Himmelman |
|
// |
|
// Permission is hereby granted, free of charge, to any person obtaining a copy |
|
// of this software and associated documentation files (the "Software"), to deal |
|
// in the Software without restriction, including without limitation the rights |
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|
// copies of the Software, and to permit persons to whom the Software is |
|
// furnished to do so, subject to the following conditions: |
|
// |
|
// The above copyright notice and this permission notice shall be included in |
|
// all copies or substantial portions of the Software. |
|
// |
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
|
// THE SOFTWARE. |
|
|
|
import Foundation |
|
|
|
public enum MappingType { |
|
case fromJSON |
|
case toJSON |
|
} |
|
|
|
/// The Mapper class provides methods for converting Model objects to JSON and methods for converting JSON to Model objects |
|
public final class Mapper<N: BaseMappable> { |
|
|
|
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, shouldIncludeNilValues: Bool = false){ |
|
self.context = context |
|
self.shouldIncludeNilValues = shouldIncludeNilValues |
|
} |
|
|
|
// MARK: Mapping functions that map to an existing object toObject |
|
|
|
/// Maps a JSON object to an existing Mappable object if it is a JSON dictionary, or returns the passed object as is |
|
public func map(JSONObject: Any?, toObject object: N) -> N { |
|
if let JSON = JSONObject as? [String: Any] { |
|
return map(JSON: JSON, toObject: object) |
|
} |
|
|
|
return object |
|
} |
|
|
|
/// Map a JSON string onto an existing object |
|
public func map(JSONString: String, toObject object: N) -> N { |
|
if let JSON = Mapper.parseJSONStringIntoDictionary(JSONString: JSONString) { |
|
return map(JSON: JSON, toObject: object) |
|
} |
|
return object |
|
} |
|
|
|
/// Maps a JSON dictionary to an existing object that conforms to Mappable. |
|
/// 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, shouldIncludeNilValues: shouldIncludeNilValues) |
|
mutableObject.mapping(map: map) |
|
return mutableObject |
|
} |
|
|
|
//MARK: Mapping functions that create an object |
|
|
|
/// Map a JSON string to an object that conforms to Mappable |
|
public func map(JSONString: String) -> N? { |
|
if let JSON = Mapper.parseJSONStringIntoDictionary(JSONString: JSONString) { |
|
return map(JSON: JSON) |
|
} |
|
|
|
return nil |
|
} |
|
|
|
/// Maps a JSON object to a Mappable object if it is a JSON dictionary or NSString, or returns nil. |
|
public func map(JSONObject: Any?) -> N? { |
|
if let JSON = JSONObject as? [String: Any] { |
|
return map(JSON: JSON) |
|
} |
|
|
|
return nil |
|
} |
|
|
|
/// 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, shouldIncludeNilValues: shouldIncludeNilValues) |
|
|
|
if let klass = N.self as? StaticMappable.Type { // Check if object is StaticMappable |
|
if var object = klass.objectForMapping(map: map) as? N { |
|
object.mapping(map: map) |
|
return object |
|
} |
|
} else if let klass = N.self as? Mappable.Type { // Check if object is Mappable |
|
if var object = klass.init(map: map) as? N { |
|
object.mapping(map: map) |
|
return object |
|
} |
|
} else if let klass = N.self as? ImmutableMappable.Type { // Check if object is ImmutableMappable |
|
do { |
|
if var object = try klass.init(map: map) as? N { |
|
object.mapping(map: map) |
|
return object |
|
} |
|
} catch let error { |
|
#if DEBUG |
|
#if !os(Linux) |
|
let exception: NSException |
|
if let mapError = error as? MapError { |
|
exception = NSException(name: .init(rawValue: "MapError"), reason: mapError.description, userInfo: nil) |
|
} else { |
|
exception = NSException(name: .init(rawValue: "ImmutableMappableError"), reason: error.localizedDescription, userInfo: nil) |
|
} |
|
exception.raise() |
|
#endif |
|
#endif |
|
} |
|
} else { |
|
// Ensure BaseMappable is not implemented directly |
|
assert(false, "BaseMappable should not be implemented directly. Please implement Mappable, StaticMappable or ImmutableMappable") |
|
} |
|
|
|
return nil |
|
} |
|
|
|
// MARK: Mapping functions for Arrays and Dictionaries |
|
|
|
/// Maps a JSON array to an object that conforms to Mappable |
|
public func mapArray(JSONString: String) -> [N]? { |
|
let parsedJSON: Any? = Mapper.parseJSONString(JSONString: JSONString) |
|
|
|
if let objectArray = mapArray(JSONObject: parsedJSON) { |
|
return objectArray |
|
} |
|
|
|
// failed to parse JSON into array form |
|
// try to parse it into a dictionary and then wrap it in an array |
|
if let object = map(JSONObject: parsedJSON) { |
|
return [object] |
|
} |
|
|
|
return nil |
|
} |
|
|
|
/// Maps a JSON object to an array of Mappable objects if it is an array of JSON dictionary, or returns nil. |
|
public func mapArray(JSONObject: Any?) -> [N]? { |
|
if let JSONArray = JSONObject as? [[String: Any]] { |
|
return mapArray(JSONArray: JSONArray) |
|
} |
|
|
|
return nil |
|
} |
|
|
|
/// Maps an array of JSON dictionary to an array of Mappable objects |
|
public func mapArray(JSONArray: [[String: Any]]) -> [N] { |
|
// map every element in JSON array to type N |
|
#if swift(>=4.1) |
|
let result = JSONArray.compactMap(map) |
|
#else |
|
let result = JSONArray.flatMap(map) |
|
#endif |
|
return result |
|
} |
|
|
|
/// Maps a JSON object to a dictionary of Mappable objects if it is a JSON dictionary of dictionaries, or returns nil. |
|
public func mapDictionary(JSONString: String) -> [String: N]? { |
|
let parsedJSON: Any? = Mapper.parseJSONString(JSONString: JSONString) |
|
return mapDictionary(JSONObject: parsedJSON) |
|
} |
|
|
|
/// Maps a JSON object to a dictionary of Mappable objects if it is a JSON dictionary of dictionaries, or returns nil. |
|
public func mapDictionary(JSONObject: Any?) -> [String: N]? { |
|
if let JSON = JSONObject as? [String: [String: Any]] { |
|
return mapDictionary(JSON: JSON) |
|
} |
|
|
|
return nil |
|
} |
|
|
|
/// Maps a JSON dictionary of dictionaries to a dictionary of Mappable objects |
|
public func mapDictionary(JSON: [String: [String: Any]]) -> [String: N]? { |
|
// map every value in dictionary to type N |
|
let result = JSON.filterMap(map) |
|
if !result.isEmpty { |
|
return result |
|
} |
|
|
|
return nil |
|
} |
|
|
|
/// Maps a JSON object to a dictionary of Mappable objects if it is a JSON dictionary of dictionaries, or returns nil. |
|
public func mapDictionary(JSONObject: Any?, toDictionary dictionary: [String: N]) -> [String: N] { |
|
if let JSON = JSONObject as? [String : [String : Any]] { |
|
return mapDictionary(JSON: JSON, toDictionary: dictionary) |
|
} |
|
|
|
return dictionary |
|
} |
|
|
|
/// Maps a JSON dictionary of dictionaries to an existing dictionary of Mappable objects |
|
public func mapDictionary(JSON: [String: [String: Any]], toDictionary dictionary: [String: N]) -> [String: N] { |
|
var mutableDictionary = dictionary |
|
for (key, value) in JSON { |
|
if let object = dictionary[key] { |
|
_ = map(JSON: value, toObject: object) |
|
} else { |
|
mutableDictionary[key] = map(JSON: value) |
|
} |
|
} |
|
|
|
return mutableDictionary |
|
} |
|
|
|
/// Maps a JSON object to a dictionary of arrays of Mappable objects |
|
public func mapDictionaryOfArrays(JSONObject: Any?) -> [String: [N]]? { |
|
if let JSON = JSONObject as? [String: [[String: Any]]] { |
|
return mapDictionaryOfArrays(JSON: JSON) |
|
} |
|
|
|
return nil |
|
} |
|
|
|
///Maps a JSON dictionary of arrays to a dictionary of arrays of Mappable objects |
|
public func mapDictionaryOfArrays(JSON: [String: [[String: Any]]]) -> [String: [N]]? { |
|
// map every value in dictionary to type N |
|
let result = JSON.filterMap { |
|
mapArray(JSONArray: $0) |
|
} |
|
|
|
if !result.isEmpty { |
|
return result |
|
} |
|
|
|
return nil |
|
} |
|
|
|
/// Maps an 2 dimentional array of JSON dictionaries to a 2 dimentional array of Mappable objects |
|
public func mapArrayOfArrays(JSONObject: Any?) -> [[N]]? { |
|
if let JSONArray = JSONObject as? [[[String: Any]]] { |
|
let objectArray = JSONArray.map { innerJSONArray in |
|
return mapArray(JSONArray: innerJSONArray) |
|
} |
|
|
|
if !objectArray.isEmpty { |
|
return objectArray |
|
} |
|
} |
|
|
|
return nil |
|
} |
|
|
|
// MARK: Utility functions for converting strings to JSON objects |
|
|
|
/// Convert a JSON String into a Dictionary<String, Any> using NSJSONSerialization |
|
public static func parseJSONStringIntoDictionary(JSONString: String) -> [String: Any]? { |
|
let parsedJSON: Any? = Mapper.parseJSONString(JSONString: JSONString) |
|
return parsedJSON as? [String: Any] |
|
} |
|
|
|
/// Convert a JSON String into an Object using NSJSONSerialization |
|
public static func parseJSONString(JSONString: String) -> Any? { |
|
let data = JSONString.data(using: String.Encoding.utf8, allowLossyConversion: true) |
|
if let data = data { |
|
let parsedJSON: Any? |
|
do { |
|
parsedJSON = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments) |
|
} catch let error { |
|
print(error) |
|
parsedJSON = nil |
|
} |
|
return parsedJSON |
|
} |
|
|
|
return nil |
|
} |
|
} |
|
|
|
extension Mapper { |
|
// MARK: Functions that create model from JSON file |
|
|
|
/// JSON file to Mappable object |
|
/// - parameter JSONfile: Filename |
|
/// - Returns: Mappable object |
|
public func map(JSONfile: String) -> N? { |
|
if let path = Bundle.main.path(forResource: JSONfile, ofType: nil) { |
|
do { |
|
let JSONString = try String(contentsOfFile: path) |
|
do { |
|
return self.map(JSONString: JSONString) |
|
} |
|
} catch { |
|
return nil |
|
} |
|
} |
|
return nil |
|
} |
|
|
|
/// JSON file to Mappable object array |
|
/// - parameter JSONfile: Filename |
|
/// - Returns: Mappable object array |
|
public func mapArray(JSONfile: String) -> [N]? { |
|
if let path = Bundle.main.path(forResource: JSONfile, ofType: nil) { |
|
do { |
|
let JSONString = try String(contentsOfFile: path) |
|
do { |
|
return self.mapArray(JSONString: JSONString) |
|
} |
|
} catch { |
|
return nil |
|
} |
|
} |
|
return nil |
|
} |
|
} |
|
|
|
extension Mapper { |
|
|
|
// MARK: Functions that create JSON from objects |
|
|
|
///Maps an object that conforms to Mappable to a JSON dictionary <String, Any> |
|
public func toJSON(_ object: N) -> [String: Any] { |
|
var mutableObject = object |
|
let map = Map(mappingType: .toJSON, JSON: [:], context: context, shouldIncludeNilValues: shouldIncludeNilValues) |
|
mutableObject.mapping(map: map) |
|
return map.JSON |
|
} |
|
|
|
///Maps an array of Objects to an array of JSON dictionaries [[String: Any]] |
|
public func toJSONArray(_ array: [N]) -> [[String: Any]] { |
|
return array.map { |
|
// convert every element in array to JSON dictionary equivalent |
|
self.toJSON($0) |
|
} |
|
} |
|
|
|
///Maps a dictionary of Objects that conform to Mappable to a JSON dictionary of dictionaries. |
|
public func toJSONDictionary(_ dictionary: [String: N]) -> [String: [String: Any]] { |
|
return dictionary.map { (arg: (key: String, value: N)) in |
|
// convert every value in dictionary to its JSON dictionary equivalent |
|
return (arg.key, self.toJSON(arg.value)) |
|
} |
|
} |
|
|
|
///Maps a dictionary of Objects that conform to Mappable to a JSON dictionary of dictionaries. |
|
public func toJSONDictionaryOfArrays(_ dictionary: [String: [N]]) -> [String: [[String: Any]]] { |
|
return dictionary.map { (arg: (key: String, value: [N])) in |
|
// convert every value (array) in dictionary to its JSON dictionary equivalent |
|
return (arg.key, self.toJSONArray(arg.value)) |
|
} |
|
} |
|
|
|
/// Maps an Object to a JSON string with option of pretty formatting |
|
public func toJSONString(_ object: N, prettyPrint: Bool = false) -> String? { |
|
let JSONDict = toJSON(object) |
|
|
|
return Mapper.toJSONString(JSONDict as Any, prettyPrint: prettyPrint) |
|
} |
|
|
|
/// Maps an array of Objects to a JSON string with option of pretty formatting |
|
public func toJSONString(_ array: [N], prettyPrint: Bool = false) -> String? { |
|
let JSONDict = toJSONArray(array) |
|
|
|
return Mapper.toJSONString(JSONDict as Any, prettyPrint: prettyPrint) |
|
} |
|
|
|
/// Converts an Object to a JSON string with option of pretty formatting |
|
public static func toJSONString(_ JSONObject: Any, prettyPrint: Bool) -> String? { |
|
let options: JSONSerialization.WritingOptions = prettyPrint ? .prettyPrinted : [] |
|
if let JSON = Mapper.toJSONData(JSONObject, options: options) { |
|
return String(data: JSON, encoding: String.Encoding.utf8) |
|
} |
|
|
|
return nil |
|
} |
|
|
|
/// Converts an Object to JSON data with options |
|
public static func toJSONData(_ JSONObject: Any, options: JSONSerialization.WritingOptions) -> Data? { |
|
if JSONSerialization.isValidJSONObject(JSONObject) { |
|
let JSONData: Data? |
|
do { |
|
JSONData = try JSONSerialization.data(withJSONObject: JSONObject, options: options) |
|
} catch let error { |
|
print(error) |
|
JSONData = nil |
|
} |
|
|
|
return JSONData |
|
} |
|
|
|
return nil |
|
} |
|
} |
|
|
|
extension Mapper where N: Hashable { |
|
|
|
/// Maps a JSON array to an object that conforms to Mappable |
|
public func mapSet(JSONString: String) -> Set<N>? { |
|
let parsedJSON: Any? = Mapper.parseJSONString(JSONString: JSONString) |
|
|
|
if let objectArray = mapArray(JSONObject: parsedJSON) { |
|
return Set(objectArray) |
|
} |
|
|
|
// failed to parse JSON into array form |
|
// try to parse it into a dictionary and then wrap it in an array |
|
if let object = map(JSONObject: parsedJSON) { |
|
return Set([object]) |
|
} |
|
|
|
return nil |
|
} |
|
|
|
/// Maps a JSON object to an Set of Mappable objects if it is an array of JSON dictionary, or returns nil. |
|
public func mapSet(JSONObject: Any?) -> Set<N>? { |
|
if let JSONArray = JSONObject as? [[String: Any]] { |
|
return mapSet(JSONArray: JSONArray) |
|
} |
|
|
|
return nil |
|
} |
|
|
|
/// Maps an Set of JSON dictionary to an array of Mappable objects |
|
public func mapSet(JSONArray: [[String: Any]]) -> Set<N> { |
|
// map every element in JSON array to type N |
|
#if swift(>=4.1) |
|
return Set(JSONArray.compactMap(map)) |
|
#else |
|
return Set(JSONArray.flatMap(map)) |
|
#endif |
|
} |
|
|
|
///Maps a Set of Objects to a Set of JSON dictionaries [[String : Any]] |
|
public func toJSONSet(_ set: Set<N>) -> [[String: Any]] { |
|
return set.map { |
|
// convert every element in set to JSON dictionary equivalent |
|
self.toJSON($0) |
|
} |
|
} |
|
|
|
/// Maps a set of Objects to a JSON string with option of pretty formatting |
|
public func toJSONString(_ set: Set<N>, prettyPrint: Bool = false) -> String? { |
|
let JSONDict = toJSONSet(set) |
|
|
|
return Mapper.toJSONString(JSONDict as Any, prettyPrint: prettyPrint) |
|
} |
|
} |
|
|
|
extension Dictionary { |
|
internal func map<K, V>(_ f: (Element) throws -> (K, V)) rethrows -> [K: V] { |
|
var mapped = [K: V]() |
|
|
|
for element in self { |
|
let newElement = try f(element) |
|
mapped[newElement.0] = newElement.1 |
|
} |
|
|
|
return mapped |
|
} |
|
|
|
internal func map<K, V>(_ f: (Element) throws -> (K, [V])) rethrows -> [K: [V]] { |
|
var mapped = [K: [V]]() |
|
|
|
for element in self { |
|
let newElement = try f(element) |
|
mapped[newElement.0] = newElement.1 |
|
} |
|
|
|
return mapped |
|
} |
|
|
|
|
|
internal func filterMap<U>(_ f: (Value) throws -> U?) rethrows -> [Key: U] { |
|
var mapped = [Key: U]() |
|
|
|
for (key, value) in self { |
|
if let newValue = try f(value) { |
|
mapped[key] = newValue |
|
} |
|
} |
|
|
|
return mapped |
|
} |
|
}
|
|
|