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

//
// Mapper.swift
// ObjectMapper
//
// Created by Tristan Himmelman on 2014-10-09.
//
// The MIT License (MIT)
//
// Copyright (c) 2014-2016 Hearst
//
// 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 {
return try klass.init(map: map) as? N
} catch let error {
#if DEBUG
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
}
} 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 == false {
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 == false {
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]]] {
var objectArray = [[N]]()
for innerJSONArray in JSONArray {
let array = mapArray(JSONArray: innerJSONArray)
objectArray.append(array)
}
if objectArray.isEmpty == false {
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
}
}