Last active
August 21, 2018 05:30
-
-
Save chriseidhof/4a8304b860450a76482c0169e8a1801a to your computer and use it in GitHub Desktop.
Type-Safe Codable Alternative
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
protocol Representable { | |
associatedtype Result | |
} | |
struct ProdR<A,B>: Representable where A: Representable, B: Representable { | |
let a: A | |
let b: B | |
typealias Result = (A.Result, B.Result) | |
} | |
struct SumR<A, B>: Representable where A: Representable, B: Representable { | |
let a: A | |
let b: B | |
typealias Result = Either<A.Result, B.Result> | |
} | |
enum Either<A, B> { | |
case l(A) | |
case r(B) | |
} | |
// Labelled | |
struct LR<A>: Representable where A: Representable { | |
let label: String | |
let value: A | |
typealias Result = A.Result | |
} | |
struct K<A>: Representable { | |
typealias Result = A | |
} | |
struct UnitR: Representable { | |
typealias Result = () | |
} | |
protocol Generic { | |
associatedtype Repr: Representable | |
static var repr: Repr { get } | |
var to: Repr.Result { get } | |
init(_ from: Repr.Result) | |
} | |
struct Address: Codable, Equatable { | |
var street: String | |
} | |
extension Address: Generic { | |
// todo generate with Sourcery | |
typealias Repr = ProdR<LR<K<String>>, UnitR> | |
static let repr: Repr = ProdR(a: LR(label: "street", value: K()), b: UnitR()) | |
var to: Repr.Result { | |
return (street, ()) | |
} | |
init(_ from: Repr.Result) { | |
self.street = from.0 | |
} | |
} | |
struct Person: Codable, Equatable { | |
var name: String | |
var age: Int | |
var test: Bool? | |
var address: Address | |
} | |
extension Person: Generic { | |
// Todo generate with Sourcery | |
typealias Repr = ProdR<LR<K<String>>,ProdR<LR<K<Int>>,ProdR<LR<K<Bool?>>, ProdR<LR<K<Address>>, UnitR>>>> | |
static let repr: Repr = ProdR(a: LR(label: "name", value: K()), b: ProdR(a: LR(label: "age", value: K()), b: ProdR(a: LR(label: "test", value: K()), b: ProdR(a: LR(label: "address", value: K()), b: UnitR())))) | |
var to: Repr.Result { | |
return (name, (age, (test, (address, ())))) | |
} | |
init(_ from: Repr.Result) { | |
self.name = from.0 | |
self.age = from.1.0 | |
self.test = from.1.1.0 | |
self.address = from.1.1.1.0 | |
} | |
} | |
extension String: Error { } | |
protocol FromJSON: Representable { | |
func build(_ from: Any) throws -> Result | |
} | |
extension ProdR: FromJSON where A: FromJSON, B: FromJSON { | |
func build(_ from: Any) throws -> Result { | |
let l = try a.build(from) | |
let r = try b.build(from) | |
return (l, r) | |
} | |
} | |
extension LR: FromJSON where A: FromJSON { | |
func build(_ from: Any) throws -> Result { | |
guard let d = from as? [String:Any] else { | |
throw "Expected a dictionary containing \(label)" | |
} | |
return try value.build(d[label] as Any) | |
} | |
} | |
extension UnitR: FromJSON { | |
func build(_ from: Any) throws -> () { | |
return () | |
} | |
} | |
// Helper protocol for nominal types / primitives | |
protocol FromJSON_ { | |
init(json value: Any) throws | |
} | |
extension String: FromJSON_ { | |
init(json value: Any) throws { | |
guard let x = value as? String else { throw "Expected a String, got \(value)" } | |
self = x | |
} | |
} | |
extension Int: FromJSON_ { | |
init(json value: Any) throws { | |
guard let x = value as? Int else { throw "Expected an Int, got \(value)" } | |
self = x | |
} | |
} | |
extension Bool: FromJSON_ { | |
init(json value: Any) throws { | |
guard let x = value as? Bool else { throw "Expected a Bool, got \(value)" } | |
self = x | |
} | |
} | |
extension Optional: FromJSON_ where Wrapped: FromJSON_ { | |
init(json value: Any) throws { | |
guard let x = value as? Wrapped else { | |
self = nil | |
return | |
} | |
self = x | |
} | |
} | |
extension K: FromJSON where A: FromJSON_ { | |
func build(_ from: Any) throws -> Result { | |
return try A(json: from) | |
} | |
} | |
extension Generic where Repr: FromJSON { | |
init(json: Any) throws { | |
self = Self(try Self.repr.build(json)) | |
} | |
} | |
// We get these for free because of the Generic extension above. | |
extension Address: FromJSON_ { } | |
extension Person: FromJSON_ { } | |
// To JSON | |
protocol ToJSONDict: Representable { | |
func to(_ value: Result) -> [String:Any] | |
} | |
protocol ToJSONKV: Representable { | |
func to(_ value: Result) -> [(String, Any)] | |
} | |
protocol ToJSONValue: Representable { | |
func to(_ value: Result) -> Any | |
} | |
extension ProdR: ToJSONDict where A: ToJSONKV, B: ToJSONDict { | |
func to(_ value: (A.Result, B.Result)) -> [String:Any] { | |
let x: [(String,Any)] = a.to(value.0) | |
return Dictionary(x, uniquingKeysWith: { $1 }).merging(b.to(value.1), uniquingKeysWith: { $1 }) | |
} | |
} | |
extension LR: ToJSONKV where A: ToJSONValue { | |
func to(_ x: A.Result) -> [(String, Any)] { | |
return [(label, value.to(x))] | |
} | |
} | |
extension UnitR: ToJSONDict { | |
func to(_ x: ()) -> [String: Any] { | |
return [:] | |
} | |
} | |
protocol ToJSONValue_ { | |
var json: Any { get } | |
} | |
extension ToJSONValue_ { | |
// default impl | |
// var json: Any { return self } | |
} | |
extension K: ToJSONValue where A: ToJSONValue_ { | |
func to(_ value: A) -> Any { | |
return value.json | |
} | |
} | |
extension String: ToJSONValue_ { | |
var json: Any { return self } | |
} | |
extension Int: ToJSONValue_ { | |
var json: Any { return self } | |
} | |
extension Bool: ToJSONValue_ { | |
var json: Any { return self } | |
} | |
extension Optional: ToJSONValue_ where Wrapped: ToJSONValue_ { | |
var json: Any { return self.map { $0.json } as Any } | |
} | |
extension Generic where Repr: ToJSONDict { | |
var json: Any { | |
return Self.repr.to(self.to) | |
} | |
} | |
extension Address: ToJSONValue_ { } | |
extension Person: ToJSONValue_ { } | |
let dict: [String:Any] = ["test": true, "age": 20, "name": "hello", "address": ["street": "Test"]] | |
let p = Person(name: "the name", age: 123, test: false, address: Address(street: "Hello")) | |
let p2 = try Person(json: p.json) | |
assert(p == p2) | |
print(p2) | |
indirect enum List<A> { | |
case tail | |
case cons(A, List<A>) | |
} | |
extension List: Generic where A: Generic { | |
static var repr: SumR<UnitR, ProdR<K<A>, K<List<A>>>> { | |
return SumR(a: UnitR(), b: ProdR(a: K(), b: K())) | |
} | |
var to: Either<(), (A, List<A>)> { | |
switch self { | |
case .tail: return .l(()) | |
case let .cons(x,xs): return .r((x,xs)) | |
} | |
} | |
init(_ from: Either<(), (A, List<A>)>) { | |
switch from { | |
case let .l(x): self = .tail | |
case let .r((x, xs)): self = .cons(x,xs) | |
} | |
} | |
typealias Repr = SumR<UnitR, ProdR<K<A>, K<List<A>>>> | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment