Created
December 28, 2014 18:17
-
-
Save andymatuschak/2b311461caf740f5726f to your computer and use it in GitHub Desktop.
A pragmatic and intentionally non-abstract solution to JSON decoding / initialization that doesn't require learning about five new operators.
This file contains 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
struct User { | |
let id: Int | |
let name: String | |
let email: String? | |
} | |
extension User: JSONDecodable { | |
static func create(id: Int, name: String, email: String?) -> User { | |
return User(id: id, name: name, email: email) | |
} | |
static func decode(json: JSONValue) { | |
return check(User.create, json["id"], json["name"], json["email"]) | |
// check() calls its fn only if the required arguments are non-nil | |
// You could readily define check() as an infix operator that takes a tuple, e.g.: | |
// return User.create?<(json["id"], json["name"], json["email"]) | |
} | |
} | |
protocol JSONDecodeable { | |
class func decode(json: JSONValue) -> Self? | |
} | |
enum JSONValue { | |
case JSONObject([String: JSONValue]) | |
case JSONArray([JSONValue]) | |
// etc | |
subscript(key: String) -> Int {} | |
subscript(key: String) -> Bool {} | |
// etc | |
} | |
func check<A, B, C, R>(fn: (A,B,C) -> R, a: A?, b: B?, c: C?) -> R? { | |
if a == nil || b == nil || c == nil { | |
return nil | |
} else { | |
return fn(a!, b!, c!) | |
} | |
} | |
func check<A, B, C, R>(fn: (A?,B,C) -> R, a: A?, b: B?, c: C?) -> R? { | |
if b == nil || c == nil { | |
return nil | |
} else { | |
return fn(a, b!, c!) | |
} | |
} | |
func check<A, B, C, R>(fn: (A,B?,C) -> R, a: A?, b: B?, c: C?) -> R? { | |
if a == nil || c == nil { | |
return nil | |
} else { | |
return fn(a!, b, c!) | |
} | |
} | |
func check<A, B, C, R>(fn: (A,B,C?) -> R, a: A?, b: B?, c: C?) -> R? { | |
if a == nil || b == nil { | |
return nil | |
} else { | |
return fn(a!, b!, c) | |
} | |
} | |
func check<A, B, C, R>(fn: (A?,B?,C) -> R, a: A?, b: B?, c: C?) -> R? { | |
if c == nil { | |
return nil | |
} else { | |
return fn(a, b, c!) | |
} | |
} | |
func check<A, B, C, R>(fn: (A?,B,C?) -> R, a: A?, b: B?, c: C?) -> R? { | |
if b == nil { | |
return nil | |
} else { | |
return fn(a, b!, c) | |
} | |
} | |
func check<A, B, C, R>(fn: (A,B?,C?) -> R, a: A?, b: B?, c: C?) -> R? { | |
if a == nil { | |
return nil | |
} else { | |
return fn(a!, b, c) | |
} | |
} | |
func check<A, B, C, R>(fn: (A?,B?,C?) -> R, a: A?, b: B?, c: C?) -> R? { | |
return fn(a, b, c) | |
} | |
// etc. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hello,
Just a quick note to tell you that for checks up to n parameters there are 2^1 + 2^2 + 2^3+....+2^(n-1)+2^n distinct combinations. I will just say(without going in to the details about how I found out) that the current Swift compiler is not really up to the task.
I believe that it might be a good approach for very practical reasons but unfortunately it is un-feasable (today?).
What might work is to use check functions up to 4-5 parameters and handle the rest manually.
What is interesting to me is the very declarative style and lack of voodoo to make the mapping work. It looks to me really easy to debug. Even more interesting is the error handling version which could produce very precise error messages so one doesn't need anymore to trace the code in order to understand errors.
Adrian