Moya-ModelMapper/Demo/Pods/ModelMapper/Sources/Mapper.swift

479 lines
22 KiB
Swift

import Foundation
/// Mapper creates strongly typed objects from a given NSDictionary based on the mapping provided by
/// implementing the Mappable protocol (see `Mappable` for an example).
public struct Mapper {
private let JSON: NSDictionary
/// Create a Mapper with a NSDictionary to use as source data
///
/// - parameter JSON: The dictionary to use for the data
public init(JSON: NSDictionary) {
self.JSON = JSON
}
// MARK: - T: RawRepresentable
/// Get a RawRepresentable value from the given field in the source data
///
/// Transparently create instances of enums and other RawRepresentables from the source data
///
/// - parameter field: The field to retrieve from the source data, can be an empty string to return the
/// entire data set
///
/// - throws: MapperError.missingFieldError the field doesn't exist
/// - throws: MapperError.typeMismatchError the value exists but doesn't match the type of the RawValue
/// - throws: MapperError.invalidRawValueError the value exists with the correct type, but the type's
/// initializer fails with the passed rawValue
///
/// - returns: The value for the given field, if it can be converted to the expected type T
public func from<T: RawRepresentable>(_ field: String) throws -> T {
let object = try self.JSONFromField(field)
guard let rawValue = object as? T.RawValue else {
throw MapperError.typeMismatchError(field: field, value: object, type: T.RawValue.self)
}
guard let value = T(rawValue: rawValue) else {
throw MapperError.invalidRawValueError(field: field, value: rawValue, type: T.self)
}
return value
}
/// Get an optional RawRepresentable value from the given field in the source data
///
/// Transparently create instances of enums and other RawRepresentables from source data
///
/// - parameter field: The field to retrieve from the source data, can be an empty string to return the
/// entire data set
///
/// - returns: The value for the given field, if it can be converted to the expected type T otherwise nil
public func optionalFrom<T: RawRepresentable>(_ field: String) -> T? {
return try? self.from(field)
}
/// Get an optional value from the given fields and source data. This returns the first non-nil value
/// produced in order based on the array of fields
///
/// - parameter fields: The array of fields to check from the source data.
///
/// - returns: The first non-nil value to be produced from the array of fields, or nil if none exist
public func optionalFrom<T: RawRepresentable>(_ fields: [String]) -> T? {
for field in fields {
if let value: T = try? self.from(field) {
return value
}
}
return nil
}
/// Get a RawRepresentable value from the specified list of fields. This returns the first value produced
/// in order based on the array of fields.
///
/// - parameter fields: The array of fields to check from the source data.
///
/// - throws: MapperError.missingFieldError if none of the fields have an acceptable value.
///
/// - returns: The first non-nil value to be produced from the array of fields.
public func from<T: RawRepresentable>(_ fields: [String]) throws -> T {
for field in fields {
if let value: T = try? self.from(field) {
return value
}
}
throw MapperError.missingFieldError(field: fields.joined(separator: ", "))
}
/// Get an array of RawRepresentable values from a field in the the source data.
///
/// - note: If T.init(rawValue:) fails given the T.RawValue from the array of source data, that value will
/// be replaced by the passed defaultValue, which defaults to nil. The resulting array is
/// flatMapped and all nils are removed. This means that any unrecognized values will be removed
/// or replaced with the default. This ensures backwards compatibility if your source data has
/// keys that your mapping layer doesn't know about yet.
///
/// - parameter field: The field to use from the source data
/// - parameter defaultValue: The value to use if the rawValue initializer fails
///
/// - throws: MapperError.typeMismatchError when the value for the key is not an array of Any
/// - throws: Any other error produced by a Convertible implementation
///
/// - returns: An array of the RawRepresentable value, with all nils removed
public func from<T: RawRepresentable>(_ field: String, defaultValue: T? = nil) throws ->
[T] where T.RawValue: Convertible, T.RawValue == T.RawValue.ConvertedType
{
let value = try self.JSONFromField(field)
guard let array = value as? [Any] else {
throw MapperError.typeMismatchError(field: field, value: value, type: [Any].self)
}
let rawValues = try array.map { try T.RawValue.fromMap($0) }
return rawValues.compactMap { T(rawValue: $0) ?? defaultValue }
}
/// Get an optional array of RawRepresentable values from a field in the the source data.
///
/// - note: If T.init(rawValue:) fails given the T.RawValue from the array of source data, that value will
/// be replaced by the passed defaultValue, which defaults to nil. The resulting array is
/// flatMapped and all nils are removed. This means that any unrecognized values will be removed
/// or replaced with the default. This ensures backwards compatibility if your source data has
/// keys that your mapping layer doesn't know about yet.
///
/// - parameter field: The field to use from the source data
/// - parameter defaultValue: The value to use if the rawValue initializer fails
///
/// - returns: An array of the RawRepresentable value, with all nils removed or nil if anything throws
public func optionalFrom<T: RawRepresentable>(_ field: String, defaultValue: T? = nil) ->
[T]? where T.RawValue: Convertible, T.RawValue == T.RawValue.ConvertedType
{
return try? self.from(field, defaultValue: defaultValue)
}
// MARK: - T: Mappable
/// Get a Mappable value from the given field in the source data
///
/// This allows you to transparently have nested Mappable values
///
/// - parameter field: The field to retrieve from the source data, can be an empty string to return the
/// entire data set
///
/// - throws: MapperError.missingFieldError if the field doesn't exist
/// - throws: MapperError.typeMismatchError if the field exists but isn't an NSDictionary
///
/// - returns: The value for the given field, if it can be converted to the expected type T
public func from<T: Mappable>(_ field: String) throws -> T {
let value = try self.JSONFromField(field)
if let JSON = value as? NSDictionary {
return try T(map: Mapper(JSON: JSON))
}
throw MapperError.typeMismatchError(field: field, value: value, type: NSDictionary.self)
}
/// Get an array of Mappable values from the given field in the source data
///
/// This allows you to transparently have nested arrays of Mappable values
///
/// - note: If any value in the array of NSDictionaries is invalid, this method throws
///
/// - parameter field: The field to retrieve from the source data, can be an empty string to return the
/// entire data set
///
/// - throws: MapperError.missingFieldError if the field doesn't exist
/// - throws: MapperError.typeMismatchError if the field exists but isn't an array of NSDictionarys
/// - throws: Any errors produced by the subsequent Mappable initializers
///
/// - returns: The value for the given field, if it can be converted to the expected type [T]
public func from<T: Mappable>(_ field: String) throws -> [T] {
let value = try self.JSONFromField(field)
if let JSON = value as? [NSDictionary] {
return try JSON.map { try T(map: Mapper(JSON: $0)) }
}
throw MapperError.typeMismatchError(field: field, value: value, type: [NSDictionary].self)
}
/// Get an optional Mappable value from the given field in the source data
///
/// This allows you to transparently have nested Mappable values
///
/// - parameter field: The field to retrieve from the source data, can be an empty string to return the
/// entire data set
///
/// - returns: The value for the given field, if it can be converted to the expected type T otherwise nil
public func optionalFrom<T: Mappable>(_ field: String) -> T? {
return try? self.from(field)
}
/// Get an optional array of Mappable values from the given field in the source data
///
/// This allows you to transparently have nested arrays of Mappable values
///
/// - note: If any value in the provided array of NSDictionaries is invalid, this method returns nil
///
/// - parameter field: The field to retrieve from the source data, can be an empty string to return the
/// entire data set
///
/// - returns: The value for the given field, if it can be converted to the expected type [T]
public func optionalFrom<T: Mappable>(_ field: String) -> [T]? {
return try? self.from(field)
}
/// Get an optional value from the given fields and source data. This returns the first non-nil value
/// produced in order based on the array of fields
///
/// - parameter fields: The array of fields to check from the source data.
///
/// - returns: The first non-nil value to be produced from the array of fields, or nil if none exist
public func optionalFrom<T: Mappable>(_ fields: [String]) -> T? {
for field in fields {
if let value: T = try? self.from(field) {
return value
}
}
return nil
}
/// Get a value from the specified list of fields. This returns the first value produced in order based on
/// the array of fields.
///
/// - parameter fields: The array of fields to check from the source data.
///
/// - throws: MapperError.missingFieldError if none of the fields have an acceptable value.
///
/// - returns: The first non-nil value to be produced from the array of fields.
public func from<T: Mappable>(_ fields: [String]) throws -> T {
for field in fields {
if let value: T = try? self.from(field) {
return value
}
}
throw MapperError.missingFieldError(field: fields.joined(separator: ", "))
}
// MARK: - T: Convertible
/// Get a Convertible value from a field in the source data
///
/// This transparently converts your types that conform to Convertible to properties on the Mappable type
///
/// - parameter field: The field to retrieve from the source data, can be an empty string to return the
/// entire data set
///
/// - throws: Any error produced by the custom Convertible implementation
///
/// - returns: The value for the given field, if it can be converted to the expected type T
public func from<T: Convertible>(_ field: String) throws -> T where T == T.ConvertedType {
return try self.from(field, transformation: T.fromMap)
}
/// Get a Convertible value from a field in the source data
///
/// This transparently converts your types that conform to Convertible to properties on the Mappable type
///
/// - parameter field: The field to retrieve from the source data, can be an empty string to return the
/// entire data set
///
/// - throws: Any error produced by the custom Convertible implementation
///
/// - note: This function is necessary because Swift does't coerce the from that returns T to an optional
///
/// - returns: The value for the given field, if it can be converted to the expected type Optional<T>
public func from<T: Convertible>(_ field: String) throws -> T? where T == T.ConvertedType {
return try self.from(field, transformation: T.fromMap)
}
/// Get an array of Convertible values from a field in the source data
///
/// This transparently converts your types that conform to Convertible to an array on the Mappable type
///
/// - parameter field: The field to retrieve from the source data, can be an empty string to return the
/// entire data set
///
/// - throws: MapperError.missingFieldError if the field doesn't exist
/// - throws: MapperError.typeMismatchError if the field exists but isn't an array of Any
/// - throws: Any error produced by the subsequent Convertible implementations
///
/// - returns: The value for the given field, if it can be converted to the expected type [T]
public func from<T: Convertible>(_ field: String) throws -> [T] where T == T.ConvertedType {
let value = try self.JSONFromField(field)
if let JSON = value as? [Any] {
return try JSON.map(T.fromMap)
}
throw MapperError.typeMismatchError(field: field, value: value, type: [Any].self)
}
/// Get a Convertible value from a field in the source data
///
/// This transparently converts your types that conform to Convertible to properties on the Mappable type
///
/// - parameter field: The field to retrieve from the source data, can be an empty string to return the
/// entire data set
///
/// - returns: The value for the given field, if it can be converted to the expected type T otherwise nil
public func optionalFrom<T: Convertible>(_ field: String) -> T? where T == T.ConvertedType {
return try? self.from(field, transformation: T.fromMap)
}
/// Get an optional array of Convertible values from a field in the source data
///
/// This transparently converts your types that conform to Convertible to an array on the Mappable type
///
/// - parameter field: The field to retrieve from the source data, can be an empty string to return the
/// entire data set
///
/// - returns: The value for the given field, if it can be converted to the expected type [T]
public func optionalFrom<T: Convertible>(_ field: String) -> [T]? where T == T.ConvertedType {
return try? self.from(field)
}
/// Get a dictionary of Convertible values from a field in the source data
///
/// This transparently converts a source dictionary to a dictionary of 2 Convertible types
///
/// - parameter field: The field to retrieve from the source data, can be an empty string to return the
/// entire data set
///
/// - throws: MapperError.typeMismatchError if the value for the given field isn't a [AnyHashable: Any]
/// - throws: Any error produced by the Convertible implementation of either expected type
///
/// - returns: A dictionary where the keys and values are created using their convertible implementations
public func from<U: Convertible, T: Convertible>(_ field: String) throws -> [U: T]
where U == U.ConvertedType, T == T.ConvertedType
{
let object = try self.JSONFromField(field)
guard let data = object as? [AnyHashable: Any] else {
throw MapperError.typeMismatchError(field: field, value: object, type: [AnyHashable: Any].self)
}
var result = [U: T]()
for (key, value) in data {
result[try U.fromMap(key)] = try T.fromMap(value)
}
return result
}
/// Get an optional dictionary of Convertible values from a field in the source data
///
/// This transparently converts a source dictionary to a dictionary of 2 Convertible types
///
/// - parameter field: The field to retrieve from the source data, can be an empty string to return the
/// entire data set
///
/// - returns: A dictionary where the keys and values are created using their convertible implementations
/// or nil if anything throws
public func optionalFrom<U: Convertible, T: Convertible>(_ field: String) -> [U: T]?
where U == U.ConvertedType, T == T.ConvertedType
{
return try? self.from(field)
}
/// Get an optional value from the given fields and source data. This returns the first non-nil value
/// produced in order based on the array of fields
///
/// - parameter fields: The array of fields to check from the source data.
///
/// - returns: The first non-nil value to be produced from the array of fields, or nil if none exist
public func optionalFrom<T: Convertible>(_ fields: [String]) -> T? where T == T.ConvertedType {
for field in fields {
if let value: T = try? self.from(field) {
return value
}
}
return nil
}
/// Get a Convertible value from the specified list of fields. This returns the first value produced in
/// order based on the array of fields.
///
/// - parameter fields: The array of fields to check from the source data.
///
/// - throws: MapperError.missingFieldError if none of the fields have an acceptable value.
///
/// - returns: The first non-nil value to be produced from the array of fields.
public func from<T: Convertible>(_ fields: [String]) throws -> T where T == T.ConvertedType {
for field in fields {
if let value: T = try? self.from(field, transformation: T.fromMap) {
return value
}
}
throw MapperError.missingFieldError(field: fields.joined(separator: ", "))
}
/// Get a Convertible value from the specified list of fields. This returns the first value produced in
/// order based on the array of fields.
///
/// - parameter fields: The array of fields to retrieve from the source data
/// - parameter transformation: The transformation function used to create the expected value
///
/// - throws: MapperError.missingFieldError if none of the fields have an acceptable value.
///
/// - returns: The value of type T for the given field
public func from<T: Convertible>(_ fields: [String], transformation: (Any) throws -> T)
throws -> T where T == T.ConvertedType
{
for field in fields {
if let value: T = try? self.from(field, transformation: transformation) {
return value
}
}
throw MapperError.missingFieldError(field: fields.joined(separator: ", "))
}
// MARK: - Custom Transformation
/// Get a typed value from the given field by using the given transformation
///
/// - parameter field: The field to retrieve from the source data, can be an empty string to
/// return the entire data set
/// - parameter transformation: The transformation function used to create the expected value
///
/// - throws: MapperError.missingFieldError if the field doesn't exist
/// - throws: Any exception thrown by the transformation function, if you're implementing the
/// transformation function you should use `MapperError`, see the documentation there for info
///
/// - returns: The value of type T for the given field
public func from<T>(_ field: String, transformation: (Any) throws -> T) throws -> T {
return try transformation(try self.JSONFromField(field))
}
/// Get an optional typed value from the given field by using the given transformation
///
/// - parameter field: The field to retrieve from the source data, can be an empty string to
/// return the entire data set
/// - parameter transformation: The transformation function used to create the expected value
///
/// - returns: The value of type T for the given field, if the transformation function doesn't throw
/// otherwise nil
public func optionalFrom<T>(_ field: String, transformation: (Any) throws -> T?) -> T? {
return (try? transformation(try self.JSONFromField(field))).flatMap { $0 }
}
/// Get an optional typed value from the given fields by using the given transformation
///
/// - parameter fields: The array of fields to retrieve from the source data, can be an empty
/// string to return the entire data set
/// - parameter transformation: The transformation function used to create the expected value
///
/// - returns: The value of type T for the given field, if the transformation function doesn't throw
/// otherwise nil
public func optionalFrom<T>(_ fields: [String], transformation: (Any) throws -> T?) -> T? {
for field in fields {
if let value = try? transformation(try self.JSONFromField(field)) {
return value
}
}
return nil
}
// MARK: - Private
/// Get the object for a given field. If an empty string is passed, return the entire data source. This
/// allows users to create objects from multiple fields in the top level of the data source
///
/// - parameter field: The field to extract from the data source, can be an empty string to return the
/// entire data set
///
/// - throws: MapperError.missingFieldError if the field doesn't exist
///
/// - returns: The object for the given field
private func JSONFromField(_ field: String) throws -> Any {
if let value = field.isEmpty ? self.JSON : self.JSON.safeValue(forKeyPath: field) {
return value
}
throw MapperError.missingFieldError(field: field)
}
}