Skip to content

Instantly share code, notes, and snippets.

@dreymonde
Last active March 28, 2019 05:44
Show Gist options
  • Save dreymonde/3a7d877e5bb6fa698f4201fdd55f1850 to your computer and use it in GitHub Desktop.
Save dreymonde/3a7d877e5bb6fa698f4201fdd55f1850 to your computer and use it in GitHub Desktop.
Next generation of Mapper, chapter 2
import Foundation
enum IndexPathValue {
case index(Int)
case key(String)
}
protocol IndexPathElement {
var indexPathValue: IndexPathValue { get }
}
extension Int: IndexPathElement {
var indexPathValue: IndexPathValue {
return .index(self)
}
}
extension String: IndexPathElement {
var indexPathValue: IndexPathValue {
return .key(self)
}
}
protocol MapProtocol {
subscript(indexPath: IndexPathElement) -> Self? { get }
var asArray: [Self]? { get }
func get<T>() -> T?
}
extension MapProtocol {
subscript(indexPath: [IndexPathElement]) -> Self? {
get {
var result = self
for index in indexPath {
if let deeped = result[index] {
result = deeped
} else {
break
}
}
return result
}
}
}
protocol Mappable {
init<Map: MapProtocol>(mapper: Mapper<Map>) throws
}
protocol MappableWithContext: Mappable {
associatedtype Context
init<Map: MapProtocol>(mapper: ContextualMapper<Map, Context>) throws
}
extension MappableWithContext {
init<Map: MapProtocol>(mapper: Mapper<Map>) throws {
let contextual = ContextualMapper<Map, Context>(mapper.map, context: nil)
try self.init(mapper: contextual)
}
}
enum MapperError: Error {
case noValue(forIndexPath: [IndexPathElement])
case wrongType
case cannotInitializeFromRawValue
case cannotRepresentAsArray
}
protocol MapperProtocol {
associatedtype Map: MapProtocol
var map: Map { get }
}
extension MapperProtocol {
fileprivate func deep(_ indexPath: [IndexPathElement]) throws -> Map {
if let value = map[indexPath] {
return value
} else {
print(map)
throw MapperError.noValue(forIndexPath: indexPath)
}
}
fileprivate func get<T>(from map: Map) throws -> T {
if let value: T = map.get() {
return value
} else {
throw MapperError.wrongType
}
}
fileprivate func array(_ map: Map) throws -> [Map] {
if let array = map.asArray {
return array
} else {
throw MapperError.cannotRepresentAsArray
}
}
func map<T>(from indexPath: IndexPathElement...) throws -> T {
let leveled = try deep(indexPath)
return try get(from: leveled)
}
func map<T: Mappable>(from indexPath: IndexPathElement...) throws -> T {
let leveled = try deep(indexPath)
return try T(mapper: Mapper(leveled))
}
func map<T: MappableWithContext>(from indexPath: IndexPathElement..., usingContext context: T.Context) throws -> T {
let leveled = try deep(indexPath)
return try T(mapper: ContextualMapper(leveled, context: context))
}
func map<T: RawRepresentable>(from indexPath: IndexPathElement...) throws -> T {
let leveled = try deep(indexPath)
let raw: T.RawValue = try get(from: leveled)
if let value = T(rawValue: raw) {
return value
} else {
throw MapperError.cannotInitializeFromRawValue
}
}
func map<T>(arrayFrom indexPath: IndexPathElement...) throws -> [T] {
let leveled = try deep(indexPath)
let array = try self.array(leveled)
return try array.map({ try get(from: $0) })
}
func map<T: Mappable>(arrayFrom indexPath: IndexPathElement...) throws -> [T] {
let leveled = try deep(indexPath)
let array = try self.array(leveled)
return try array.map({ try T(mapper: Mapper($0)) })
}
func map<T: MappableWithContext>(arrayFrom indexPath: IndexPathElement..., usingContext context: T.Context) throws -> [T] {
let leveled = try deep(indexPath)
let array = try self.array(leveled)
return try array.map({ try T(mapper: ContextualMapper($0, context: context)) })
}
func map<T: RawRepresentable>(arrayFrom indexPath: IndexPathElement...) throws -> [T] {
let leveled = try deep(indexPath)
let array = try self.array(leveled)
return try array.map({
let raw: T.RawValue = try get(from: $0)
if let value = T(rawValue: raw) {
return value
} else {
throw MapperError.cannotInitializeFromRawValue
}
})
}
}
struct Mapper<Map: MapProtocol>: MapperProtocol {
let map: Map
init(_ map: Map) {
self.map = map
}
}
struct ContextualMapper<Map: MapProtocol, Context>: MapperProtocol {
let map: Map
let context: Context?
init(_ map: Map, context: Context?) {
self.map = map
self.context = context
}
func map<T: MappableWithContext where T.Context == Context>(withContextFrom indexPath: IndexPathElement...) throws -> T {
let leveled = try deep(indexPath)
return try T(mapper: ContextualMapper(leveled, context: context))
}
func map<T: MappableWithContext where T.Context == Context>(arrayWithContextFrom indexPath: IndexPathElement...) throws -> [T] {
let leveled = try deep(indexPath)
let array = try self.array(leveled)
return try array.map({ try T(mapper: ContextualMapper($0, context: self.context)) })
}
}
enum ThatThingOne: MapProtocol {
case number(Int)
case string(String)
case array([ThatThingOne])
case dict([String: ThatThingOne])
subscript(indexPath: IndexPathElement) -> ThatThingOne? {
guard case .key(let key) = indexPath.indexPathValue else {
return nil
}
if case .dict(let dict) = self {
return dict[key]
} else {
return nil
}
}
func get<T>() -> T? {
switch self {
case .number(let value as T): return value
case .string(let value as T): return value
case .array(let value as T): return value
case .dict(let value as T): return value
default:
return nil
}
}
var asArray: [ThatThingOne]? {
if case .array(let array) = self {
return array
}
return nil
}
}
extension Mappable {
init?(thatThingOne: ThatThingOne) {
let mapper = Mapper(thatThingOne)
try? self.init(mapper: mapper)
}
}
extension MappableWithContext {
init?(thatThingOne: ThatThingOne, withContext context: Context) {
let mapper = ContextualMapper(thatThingOne, context: context)
try? self.init(mapper: mapper)
}
}
enum StringAny: MapProtocol {
case dict([String: Any])
case any(Any)
subscript(indexPath: IndexPathElement) -> StringAny? {
guard case .key(let key) = indexPath.indexPathValue else {
return nil
}
switch self {
case .dict(let dict):
return dict[key].map({ .any($0) })
case .any(let any):
return (any as? [String: Any])?[key].map({ .any($0) })
}
}
func get<T>() -> T? {
switch self {
case .any(let any):
return any as? T
case .dict(let dict):
return dict as? T
}
}
var asArray: [StringAny]? {
if case .any(let any) = self {
if let anies = any as? [Any] {
return anies.map({ .any($0) })
}
if let dicts = any as? [[String: Any]] {
return dicts.map({ .dict($0) })
}
return nil
}
return nil
}
}
extension Mappable {
init?(fromDictionary dictionary: [String: Any]) {
let mapper = Mapper(StringAny.dict(dictionary))
try? self.init(mapper: mapper)
}
}
extension MappableWithContext {
init?(fromDictionary dictionary: [String: Any], withContext context: Context?) {
let mapper = ContextualMapper(StringAny.dict(dictionary), context: context)
try? self.init(mapper: mapper)
}
}
struct Human: Mappable {
let id: Int
init<Map: MapProtocol>(mapper: Mapper<Map>) throws {
self.id = try mapper.map(from: "id")
}
}
struct Top: Mappable {
let male: Human
init<Map: MapProtocol>(mapper: Mapper<Map>) throws {
self.male = try mapper.map(from: "human")
}
}
let human: [String: Any] = ["id": 5]
let repr: [String: Any] = ["human": human]
let top = Top(fromDictionary: repr)!
print(top)
// Top(male: Human(id: 5))
let humanTTO = ThatThingOne.dict(["human": .dict(["id": .number(7)])])
let topi = Top(thatThingOne: humanTTO)!
print(topi)
// Top(male: Human(id: 7))
struct Guitar: Mappable {
enum Wood: String {
case black, brown
}
enum Strings: Int {
case six = 6, seven = 7
}
let wood: Wood
let strings: Strings
init<Map: MapProtocol>(mapper: Mapper<Map>) throws {
self.wood = try mapper.map(from: "wood")
self.strings = try mapper.map(from: "strings")
}
}
let blackSeven: [String: Any] = ["wood": "black", "strings": 7]
let guitar = Guitar(fromDictionary: blackSeven)!
print(guitar)
// Guitar(wood: Guitar.Wood.black, strings: Guitar.Strings.seven)
struct Guitars: Mappable {
let guitars: [Guitar]
init<Map: MapProtocol>(mapper: Mapper<Map>) throws {
self.guitars = try mapper.map(arrayFrom: "guitars")
}
}
let brownSix: [String: Any] = ["wood": "brown", "strings": 6]
let guitarsDict: [[String: Any]] = [blackSeven, brownSix]
let guitars = Guitars(fromDictionary: ["guitars": guitarsDict])!
print(guitars)
// Guitars(guitars: [Guitar(wood: Guitar.Wood.black, strings: Guitar.Strings.seven), map.Guitar(wood: Guitar.Wood.brown, strings: Guitar.Strings.six)])
struct Strings: Mappable {
let strings: [String]
init<Map: MapProtocol>(mapper: Mapper<Map>) throws {
self.strings = try mapper.map(arrayFrom: "strings")
}
}
let anyStrings: [Any] = ["Give", "Take"]
let mappedStrings = Strings(fromDictionary: ["strings": anyStrings])!
print(mappedStrings)
// Strings(strings: ["Give", "Take"])
enum BatFamilyMappingContext {
case normal
case json
case mongo
}
struct Batman: MappableWithContext {
let coolnessRate: Int
init<Map: MapProtocol>(mapper: ContextualMapper<Map, BatFamilyMappingContext>) throws {
let context = mapper.context ?? .normal
switch context {
case .normal:
coolnessRate = try mapper.map(from: "coolnessRate")
case .json:
coolnessRate = try mapper.map(from: "coolness_rate")
case .mongo:
coolnessRate = try mapper.map(from: "rate")
}
}
}
struct BatMobile: MappableWithContext {
let batmanInside: Batman
init<Map: MapProtocol>(mapper: ContextualMapper<Map, BatFamilyMappingContext>) throws {
self.batmanInside = try mapper.map(withContextFrom: "batman")
}
}
let coolness: [String: Any] = ["coolness_rate": 99]
let batmanDict: [String: Any] = ["batman": coolness]
let batMobile = BatMobile(fromDictionary: batmanDict, withContext: .json)!
print(batMobile)
// BatMobile(batmanInside: Batman(coolnessRate: 99))
struct RegularBatmobileAndString: Mappable {
let batmobile: BatMobile
let string: String
init<Map: MapProtocol>(mapper: Mapper<Map>) throws {
self.batmobile = try mapper.map(from: "batmobile", usingContext: .json)
self.string = try mapper.map(from: "string")
}
}
let regularBatmobileDict: [String: Any] = ["batmobile": batmanDict, "string": "Religion (Honeymoon, 2016)"]
let regularBatmobileAndString = RegularBatmobileAndString(fromDictionary: regularBatmobileDict)!
print(regularBatmobileAndString)
// RegularBatmobileAndString(batmobile: BatMobile(batmanInside: Batman(coolnessRate: 99)), string: "Religion (Honeymoon, 2016)")
struct DeepMapping: Mappable {
let deepString: String
init<Map: MapProtocol>(mapper: Mapper<Map>) throws {
self.deepString = try mapper.map(from: "deep", "deeper", "evenDeeper")
}
}
let evenDeeper: [String: Any] = ["evenDeeper": "heeveear"]
let deeper: [String: Any] = ["deeper": evenDeeper]
let deep: [String: Any] = ["deep": deeper]
let deepString = DeepMapping(fromDictionary: deep)!
print(deepString)
// DeepMapping(deepString: "heeveear")
struct JustDict: Mappable {
let dict: [String: Any]
init<Map: MapProtocol>(mapper: Mapper<Map>) throws {
self.dict = try mapper.map()
}
}
let jdd: [String: Any] = ["A": 5, "B": "b"]
let jdict = JustDict(fromDictionary: jdd)!
print(jdict)
// JustDict(dict: ["B": "b", "A": 5])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment