Efficient JSON in Swift with Functional Concepts and Generics

Tony DiPasquale

A few months ago Apple introduced a new programming language, Swift, that left us excited about the future of iOS and OS X development. People were jumping into Swift with Xcode Beta1 immediately and it didn’t take long to realize that parsing JSON, something almost every app does, was not going to be as easy as in Objective-C. Swift being a statically typed language meant we could no longer haphazardly throw objects into typed variables and have the compiler trust us that it was actually the type we claimed it would be. Now, in Swift, the compiler is doing the checking, making sure we don’t accidentally cause runtime errors. This allows us to lean on the compiler to create bug free code, but means we have to do a bit more work to make it happy. In this post, I discuss a method of parsing JSON APIs that uses functional concepts and Generics to make readable and efficient code.

Request the User Model

The first thing we need is a way to parse the data we receive from a network request into JSON. In the past, we’ve used NSJSONSerialization.JSONObjectWithData(NSData, Int, &NSError) which gives us an optional JSON data type and a possible error if there were problems with the parsing. The JSON object data type in Objective-C is NSDictionary which can hold any object in its values. With Swift, we have a new dictionary type that requires us to specify the types held within. JSON objects now map to Dictionary<String, AnyObject>. AnyObject is used because a JSON value could be a String, Double, Bool, Array, Dictionary or null. When we try to use the JSON to populate a model we’ve created, we’ll have to test that each key we get from the JSON dictionary is of that model’s property type.

As an example, let’s look at a user model:

struct User {
  let id: Int
  let name: String
  let email: String
}

Now let’s take a look at what a request and response for the current user might look like:

func getUser(request: NSURLRequest, callback: (User) -> ()) {
  let task = NSURLSession.sharedSession().dataTaskWithRequest(request) { data, urlResponse, error in
    var jsonErrorOptional: NSError?
    let jsonOptional: AnyObject! = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions(0), error: &jsonErrorOptional)

    if let json = jsonOptional as? Dictionary<String, AnyObject> {
      if let id = json["id"] as AnyObject? as? Int { // Currently in beta 5 there is a bug that forces us to cast to AnyObject? first
        if let name = json["name"] as AnyObject? as? String {
          if let email = json["email"] as AnyObject? as? String {
            let user = User(id: id, name: name, email: email)
            callback(user)
          }
        }
      }
    }
  }
  task.resume()
}

After a lot of if-let statements, we finally have our User object. You can imagine that a model with more properties will just get uglier and uglier. Also, we are not handling any errors so if any of the steps don’t succeed, we have nothing. Finally, we would have to write this code for every model we want from the API, which would be a lot of code duplication.

Before we start to refactor, let’s define some typealias‘s to simplify the JSON types.

typealias JSON = AnyObject
typealias JSONDictionary = Dictionary<String, JSON>
typealias JSONArray = Array<JSON>

Refactoring: Add Error Handling

First, we will refactor our function to handle errors by introducing the first functional programming concept, the Either<A, B> type. This will let us return the user object when everything runs smoothly or an error when it doesn’t. We can implement an Either<A, B> type in Swift like this:

enum Either<A, B> {
  case Left(A)
  case Right(B)
}

We can use Either<NSError, User>as the type we’ll pass to our callback so the caller can handle the successfully parsed User or the error.

func getUser(request: NSURLRequest, callback: (Either<NSError, User>) -> ()) {
  let task = NSURLSession.sharedSession().dataTaskWithRequest(request) { data, urlResponse, error in
    // if the response returned an error send it to the callback
    if let err = error {
      callback(.Left(err))
      return
    }

    var jsonErrorOptional: NSError?
    let jsonOptional: JSON! = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions(0), error: &jsonErrorOptional)

    // if there was an error parsing the JSON send it back
    if let err = jsonErrorOptional {
      callback(.Left(err))
      return
    }

    if let json = jsonOptional as? JSONDictionary {
      if let id = json["id"] as AnyObject? as? Int {
        if let name = json["name"] as AnyObject? as? String {
          if let email = json["email"] as AnyObject? as? String {
            let user = User(id: id, name: name, email: email)
            callback(.Right(user))
            return
          }
        }
      }
    }

    // if we couldn't parse all the properties then send back an error
    callback(.Left(NSError()))
  }
  task.resume()
}

Now the function calling our getUser can switch on the Either and do something with the user or display the error.

getUser(request) { either in
  switch either {
  case let .Left(error):
    // display error message

  case let .Right(user):
    // do something with user
  }
}

We will simplify this a bit by assuming that the Left will always be an NSError. Instead let’s use a different type Result<A> which will either hold the value we are looking for or an error. It’s implementation might look like this:

enum Result<A> {
  case Error(NSError)
  case Value(A)
}

In the current version of Swift (Beta 5), the above Result type will cause compiler errors. Swift needs to know what types we’ll be placing inside the enum’s cases. We can create a constant class to hold onto a generic value for us.

final class Box<A> {
  let value: A

  init(_ value: A) {
    self.value = value
  }
}

enum Result<A> {
  case Error(NSError)
  case Value(Box<A>)
}

Replacing Either with Result will look like this:

func getUser(request: NSURLRequest, callback: (Result<User>) -> ()) {
  let task = NSURLSession.sharedSession().dataTaskWithRequest(request) { data, urlResponse, error in
    // if the response returned an error send it to the callback
    if let err = error {
      callback(.Error(err))
      return
    }

    var jsonErrorOptional: NSError?
    let jsonOptional: JSON! = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions(0), error: &jsonErrorOptional)

    // if there was an error parsing the JSON send it back
    if let err = jsonErrorOptional {
      callback(.Error(err))
      return
    }

    if let json = jsonOptional as? JSONDictionary {
      if let id = json["id"] as AnyObject? as? Int {
        if let name = json["name"] as AnyObject? as? String {
          if let email = json["email"] as AnyObject? as? String {
            let user = User(id: id, name: name, email: email)
            callback(.Value(Box(user)))
            return
          }
        }
      }
    }

    // if we couldn't parse all the properties then send back an error
    callback(.Error(NSError()))
  }
  task.resume()
}
getUser(request) { result in
  switch result {
  case let .Error(error):
    // display error message

  case let .Value(boxedUser):
    let user = boxedUser.value
    // do something with user
  }
}

Not a big change but let’s keep going.

Refactoring: Eliminate Type Checking Tree

Next, we will get rid of the ugly JSON parsing by creating separate JSON parsers for each type. We only have a String, Int, and Dictionary in our object so we need three functions to parse those types.

func JSONString(object: JSON?) -> String? {
  return object as? String
}

func JSONInt(object: JSON?) -> Int? {
  return object as? Int
}

func JSONObject(object: JSON?) -> JSONDictionary? {
  return object as? JSONDictionary
}

Now the JSON parsing will look like this:

if let json = JSONObject(jsonOptional) {
  if let id = JSONInt(json["id"]) {
    if let name = JSONString(json["name"]) {
      if let email = JSONString(json["email"]) {
        let user = User(id: id, name: name, email: email)
      }
    }
  }
}

Using these functions we’ll still need a bunch of if-let syntax. The functional programming concepts Monads, Applicative Functors, and Currying will help to condense this parsing. First, let’s look at the Maybe Monad which is similar to Swift optionals. Monads have a bind operator which, when used with optionals, allows us to bind an optional with a function that takes a non-optional and returns an optional. If the first optional is .None then it returns .None, otherwise it unwraps the first optional and applies the function to it.

infix operator >>> { associativity left precedence 150 }

func >>><A, B>(a: A?, f: A -> B?) -> B? {
  if let x = a {
    return f(x)
  } else {
    return .None
  }
}

In other functional languages, >>= is used for bind; however, in Swift that operator is used for bitshifting so we will use >>> instead. Applying this to the JSON parsing we get:

if let json = jsonOptional >>> JSONObject {
  if let id = json["id"] >>> JSONInt {
    if let name = json["name"] >>> JSONString {
      if let email = json["email"] >>> JSONString {
        let user = User(id: id, name: name, email: email)
      }
    }
  }
}

Then we can remove the optional parameters from our parsers:

func JSONString(object: JSON) -> String? {
  return object as? String
}

func JSONInt(object: JSON) -> Int? {
  return object as? Int
}

func JSONObject(object: JSON) -> JSONDictionary? {
  return object as? JSONDictionary
}

Functors have an fmap operator for applying functions to values wrapped in some context. Applicative Functors also have an apply operator for applying wrapped functions to values wrapped in some context. The context here is an Optional which wraps our value. This means that we can combine multiple optional values with a function that takes multiple non-optional values. If all values are present, .Some, then we get a result wrapped in an optional. If any of the values are .None, we get .None. We can define these operators in Swift like this:

infix operator <^> { associativity left } // Functor's fmap (usually <$>)
infix operator <*> { associativity left } // Applicative's apply

func <^><A, B>(f: A -> B, a: A?) -> B? {
  if let x = a {
    return f(x)
  } else {
    return .None
  }
}

func <*><A, B>(f: (A -> B)?, a: A?) -> B? {
  if let x = a {
    if let fx = f {
      return fx(x)
    }
  }
  return .None
}

Before we put it all together, we will need to manually curry our User’s init since Swift doesn’t support auto-currying. Currying means that if we give a function fewer parameters than it takes, it will return a function that takes the remaining parameters. Our User model will now look like this:

struct User {
  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)
  }
}

Putting it all together, our JSON parsing now looks like this:

if let json = jsonOptional >>> JSONObject {
  let user = User.create <^>
              json["id"]    >>> JSONInt    <*>
              json["name"]  >>> JSONString <*>
              json["email"] >>> JSONString
}

If any of our parser’s return .None then user will be .None. This looks much better, but we’re not done yet.

Now, our getUser function looks like this:

func getUser(request: NSURLRequest, callback: (Result<User>) -> ()) {
  let task = NSURLSession.sharedSession().dataTaskWithRequest(request) { data, urlResponse, error in
    // if the response returned an error send it to the callback
    if let err = error {
      callback(.Error(err))
      return
    }

    var jsonErrorOptional: NSError?
    let jsonOptional: JSON! = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions(0), error: &jsonErrorOptional)

    // if there was an error parsing the JSON send it back
    if let err = jsonErrorOptional {
      callback(.Error(err))
      return
    }

    if let json = jsonOptional >>> JSONObject {
      let user = User.create <^>
                  json["id"]    >>> JSONInt    <*>
                  json["name"]  >>> JSONString <*>
                  json["email"] >>> JSONString
      if let u = user {
        callback(.Value(Box(u)))
        return
      }
    }

    // if we couldn't parse all the properties then send back an error
    callback(.Error(NSError()))
  }
  task.resume()
}

Refactoring: Remove Multiple Returns with Bind

Notice that we’re calling callback four times in the previous function. If we were to forget one of the return statements, we could introduce a bug. We can eliminate this potential bug and clean up this function further by first breaking up this function into 3 distinct parts: parse the response, parse the data into JSON, and parse the JSON into our User object. Each of these steps takes one input and returns the next step’s input or an error. This sounds like a perfect case for using bind with our Result type.

The parseResponse function will need a Result with data and the status code of the response. The iOS API only gives us NSURLResponse and keeps the data separate, so we will make a small struct to help out here:

struct Response {
  let data: NSData
  let statusCode: Int = 500

  init(data: NSData, urlResponse: NSURLResponse) {
    self.data = data
    if let httpResponse = urlResponse as? NSHTTPURLResponse {
      statusCode = httpResponse.statusCode
    }
  }
}

Now we can pass our parseResponse function a Response and check the response for errors before handing back the data.

func parseResponse(response: Response) -> Result<NSData> {
  let successRange = 200..<300
  if !contains(successRange, response.statusCode) {
    return .Error(NSError()) // customize the error message to your liking
  }
  return .Value(Box(response.data))
}

The next functions will require us to transform an optional to a Result type so let’s make one quick abstraction before we move on.

func resultFromOptional<A>(optional: A?, error: NSError) -> Result<A> {
  if let a = optional {
    return .Value(Box(a))
  } else {
    return .Error(error)
  }
}

Next up is our data to JSON function:

func decodeJSON(data: NSData) -> Result<JSON> {
  let jsonOptional: JSON! = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions(0), error: &jsonErrorOptional)
  return resultFromOptional(jsonOptional, NSError()) // use the error from NSJSONSerialization or a custom error message
}

Then, we add our JSON to model decoding on the model itself:

struct User {
  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) -> Result<User> {
    let user = JSONObject(json) >>> { dict in
      User.create <^>
          dict["id"]    >>> JSONInt    <*>
          dict["name"]  >>> JSONString <*>
          dict["email"] >>> JSONString
    }
    return resultFromOptional(user, NSError()) // custom error message
  }
}

Before we combine it all, let’s extend bind, >>>, to also work with the Result type:

func >>><A, B>(a: Result<A>, f: A -> Result<B>) -> Result<B> {
  switch a {
  case let .Value(x):     return f(x.value)
  case let .Error(error): return .Error(error)
  }
}

And add a custom initializer to Result:

enum Result<A> {
  case Error(NSError)
  case Value(Box<A>)

  init(_ error: NSError?, _ value: A) {
    if let err = error {
      self = .Error(err)
    } else {
      self = .Value(Box(value))
    }
  }
}

Now, we combine all these functions with the bind operator.

func getUser(request: NSURLRequest, callback: (Result<User>) -> ()) {
  let task = NSURLSession.sharedSession().dataTaskWithRequest(request) { data, urlResponse, error in
    let responseResult = Result(error, Response(data: data, urlResponse: urlResponse))
    let result = responseResult >>> parseResponse
                                >>> decodeJSON
                                >>> User.decode
    callback(result)
  }
  task.resume()
}

Wow, even writing this again, I’m excited with this result. You might think, “This is really cool. Can’t wait to use it!”, but we’re not done yet!

Refactoring: Type Agnostic using Generics

This is great but we still have to write this for every model we want to get. We can use Generics to make this completely abstracted.

We introduce a JSONDecodable protocol and tell our function that the type we want back must conform to that protocol. The protocol looks like this:

protocol JSONDecodable {
  class func decode(json: JSON) -> Self?
}

Next, we write a function that will decode any model that conforms to JSONDecodable into a Result:

func decodeObject<A: JSONDecodable>(json: JSON) -> Result<A> {
  return resultFromOptional(A.decode(json), NSError()) // custom error
}

Now make User conform:

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
  }
}

We changed our User decoder function to return an optional User instead of a Result<User>. This allows us to have an abstracted function that calls resultFromOptional after a decode instead of having to call that in every model decode function.

Finally, we will abstract the parsing and decoding from the performRequest function for readability. We now have our final performRequest and parseResult functions:

func performRequest<A: JSONDecodable>(request: NSURLRequest, callback: (Result<A>) -> ()) {
  let task = NSURLSession.sharedSession().dataTaskWithRequest(request) { data, urlResponse, error in
    callback(parseResult(data, urlResponse, error))
  }
  task.resume()
}

func parseResult<A: JSONDecodable>(data: NSData!, urlResponse: NSURLResponse!, error: NSError!) -> Result<A> {
  let responseResult = Result(error, Response(data: data, urlResponse: urlResponse))
  return responseResult >>> parseResponse
                        >>> decodeJSON
                        >>> decodeObject
}

Further Learning

The example code is available on GitHub.

If you are curious about functional programming or any of the concepts discussed in this post, check out Haskell and specifically this post from the Learn You a Haskell book. Also, check out Pat Brisbin’s post about options parsing using the Applicative.