Skip to content

Instantly share code, notes, and snippets.

@chriseidhof
Last active August 21, 2018 05:30
Show Gist options
  • Save chriseidhof/4a8304b860450a76482c0169e8a1801a to your computer and use it in GitHub Desktop.
Save chriseidhof/4a8304b860450a76482c0169e8a1801a to your computer and use it in GitHub Desktop.
Type-Safe Codable Alternative
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