Real World JSON Parsing with Swift

Tony DiPasquale

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.