Last time, we looked at using concepts from Functional Programming and
Generics to parse JSON received
from a server into a User
model. The final result of the JSON parsing looked like this:
struct User: JSONDecodable {
let id: Int
let name: String
let email: String
static func create(id: Int)(name: String)(email: String) -> User {
return User(id: id, name: name, email: email)
}
static func decode(json: JSON) -> User? {
return _JSONObject(json) >>> { d in
User.create <^>
d["id"] >>> _JSONInt <*>
d["name"] >>> _JSONString <*>
d["email"] >>> _JSONString
}
}
}
This is great, but in the real world the objects we get back from an API will not always be perfect. Sometimes an object will have only a few important keys and the rest can be retrieved later.
For example, when we fetch the current user we want all of the user’s info, but
when we fetch a user by their id
we don’t want the email for security
reasons. To hide the email, the server will only respond with the id
and
name
for all users that are not the current user. To reuse the same User
object, it’s more realistic to describe a User
like this:
struct User {
let id: Int
let name: String
let email: String?
}
You can see that the user’s email property is now an optional String
. If you
remember, the <^>
(fmap) and <*>
(apply) operators ensure that we only get
our User
struct if the JSON
contains all the keys; otherwise, we get .None
. If the email returned from the
server is .None
or nil
, the decode function will fail. We can fix this by
adding a pure
function.
pure
is a function that takes a value without context and puts that value into
a minimal context. Swift optionals are values in a “there-or-not” context;
therefore, pure
means .Some(value)
. The implementation is simply:
func pure<A>(a: A) -> A? {
return .Some(a)
}
Now we can use it to parse a User
who may or may not have an email:
struct User: JSONDecodable {
let id: Int
let name: String
let email: String?
static func create(id: Int)(name: String)(email: String?) -> User {
return User(id: id, name: name, email: email)
}
static func decode(json: JSON) -> User? {
return _JSONObject(json) >>> { d in
User.create <^>
d["id"] >>> _JSONInt <*>
d["name"] >>> _JSONString <*>
pure(d["email"] >>> _JSONString)
}
}
}
Without pure
, d["email"] >>> _JSONString
would return .None
if there were
no "email"
key within d
. <*>
looks for an optional and when it sees
.None
, it would stop creating the User
and return .None
for the whole
function. However, when we call pure
on the result, if the email was not
present we’ll get .Some(.None)
and <*>
will accept and unwrap the optional
then pass .None
into the initializer.
More Type Inference
The above code works great, but there is still a lot of syntax we need to write
in order to create our User
. We can use Swift’s type inference to refactor
this code.
We have been using a few functions to parse a JSON AnyObject
type into the type that the create
function
needs. With type inference, we can use the definition of the create
function
to tell the JSON parsing function what type we’re looking for. Currently,
_JSONInt
and _JSONString
look like this:
func _JSONInt(json: JSON) -> Int? {
return json as? Int
}
func _JSONString(json: JSON) -> String? {
return json as? String
}
It’s easy to see that these functions are very similar, in fact, they only differ by the type. Sounds like a use for Generics.
func _JSONParse<A>(json: JSON) -> A? {
return json as? A
}
Now we can use _JSONParse
in our decode
function instead of needing a
specific parsing function for every JSON type.
struct User: JSONDecodable {
let id: Int
let name: String
let email: String?
static func create(id: Int)(name: String)(email: String?) -> User {
return User(id: id, name: name, email: email)
}
static func decode(json: JSON) -> User? {
return _JSONParse(json) >>> { (d: JSONObject) in
User.create <^>
d["id"] >>> _JSONParse <*>
d["name"] >>> _JSONParse <*>
pure(d["email"] >>> _JSONParse)
}
}
}
This works because User.create
is a function that takes an Int
and it is
being applied to d["id"] >>> _JSONParse
. The compiler will infer that the
generic type within _JSONParse
has to be an Int
.
You’ll notice that in order to use _JSONParse
in place of _JSONObject
we
had to cast d
to a JSONObject
so that _JSONParse
can infer the type.
Our decoding function is getting better but there is still a lot of duplication.
It would be nice to eliminate all the calls to _JSONParse
. These lines are all
similar except for the key being used to extract the JSON value. We can abstract this code to save on
duplication. While we do that we can make a similar function to abstract any
value that also needs to call pure
.
func extract<A>(json: JSONObject, key: String) -> A? {
return json[key] >>> _JSONParse
}
func extractPure<A>(json: JSONObject, key: String) -> A?? {
return pure(json[key] >>> _JSONParse)
}
And now we have:
struct User: JSONDecodable {
let id: Int
let name: String
let email: String?
static func create(id: Int)(name: String)(email: String?) -> User {
return User(id: id, name: name, email: email)
}
static func decode(json: JSON) -> User? {
return _JSONParse(json) >>> { d in
User.create <^>
extract(d, "id") <*>
extract(d, "name") <*>
extractPure(d, "email")
}
}
}
Now that we have the extract
function that takes a JSONObject
as its first
parameter, we can remove the type cast on d
because it is inferred to be a
JSONObject
by passing it into extract
.
extract
and extractPure
are a lot to type every time and it could be more
readable if it were infix. Let’s create a couple custom operators to do the job
for us. We’ll use <|
and <|?
, which are inspired from Haskell’s popular
JSON parsing library, Aeson. Aeson uses .:
and .:?
, but those are
illegal operators in Swift, so we’ll use the <|
version instead.
NOTE: ?
is illegal to use in a custom operator in Swift 1.0. This is solved
in Swift 1.1 which is currently in Beta 2. You can use <|*
as an alternative
to <|?
for now.
infix operator <| { associativity left precedence 150 }
infix operator <|? { associativity left precedence 150 }
func <|<A>(json: JSONObject, key: String) -> A? {
return json[key] >>> _JSONParse
}
func <|?<A>(json: JSONObject, key: String) -> A?? {
return pure(json[key] >>> _JSONParse)
}
Now we can use these operators in out User
decoding. We’ll also move the
operators to the front of the line for better style.
struct User: JSONDecodable {
let id: Int
let name: String
let email: String?
static func create(id: Int)(name: String)(email: String?) -> User {
return User(id: id, name: name, email: email)
}
static func decode(json: JSON) -> User? {
return _JSONParse(json) >>> { d in
User.create
<^> d <| "id"
<*> d <| "name"
<*> d <|? "email"
}
}
}
Wow! Using Generics and relying on Swift’s type inference can really reduce the amount of code we have to write. What’s really interesting is how close we can get to a purely functional programming language like Haskell. Using Aeson in Haskell, decoding a User would look like this:
instance FromJSON User where
parseJSON (Object o) = User
<$> o .: "id"
<*> o .: "name"
<*> o .:? "email"
parseJSON _ = mzero
Conclusion
We’ve come a long way since the first post. Let’s bring back the old way
and look how it compares to what we can do now excluding the NSData
to
AnyObject?
conversion.
Original Method
extension User {
static func decode(json: AnyObject) -> User? {
if let jsonObject = json as? [String:AnyObject] {
if let id = jsonObject["id"] as AnyObject? as? Int {
if let name = jsonObject["name"] as AnyObject? as? String {
if let email = jsonObject["email"] as AnyObject? {
return User(id: id, name: name, email: email as? String)
}
}
}
}
return .None
}
}
Improved Method
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: JSON) -> User? {
return _JSONParse(json) >>> { d in
User.create
<^> d <| "id"
<*> d <| "name"
<*> d <|? "email"
}
}
}
The related code can be found on GitHub.