Introducing Argo 1.0: More Power, More Fun

Tony DiPasquale

Argo 1.0 is out! We wanted to slim down Argo while maintaining usability and give it more power, so we sifted through every line and asked “Is this needed?”, “Is there a better way?”, and “Is this named properly?”. What we’re left with is an all-around better Argo that’s easier to use and debug.

So, what’s new?

Decoded

The biggest change to Argo 1.0 is the introduction of the Decoded<T> type. In previous versions of Argo, we’ve relied heavily on Swift’s Optionals to represent the result of decoding the JSON into a model. .None meant failure and .Some(T) meant success. This worked fine for most cases but broke down if you wanted more context around why the decoding failed. Users were forced to always use an Optional result and could not take advantage of a more complex custom type like Result.

We’ve replaced Optional with a Decoded<T> type that represents three possible outcomes of decoding: Success, Type Mismatch, or Missing Key. Success is the same as .Some was before, representing a successful decoding. The two new error types allow us to retain a more detailed reason for failure. .TypeMismatch means that the type needed for the model doesn’t match the type in the JSON. .MissingKey means that the key requested from the JSON doesn’t exist or is NULL.

We reworked the JSONDecodable protocol to have the decode function return the new Decoded<T> type, where T is the DecodedType typealias. Let’s take a look at what decoding a User used to look like and what it looks like with Argo 1.0.

Given a User object represented in JSON like this:

{
  "id": 5,
  "name": "Gob",
  "email": "gob@bananastand.com"
}

Before Argo 1.0, we would implement the decode function like so:

struct User: JSONDecodable {
  static func create(id: Int)(name: String)(email: String) -> User {
    return User(id: id, name: name, email: email)
  }

  static func decode(j: JSONValue) -> User? {
    return User.create
      <^> j <| "id"
      <*> j <| "name"
      <*> j <| "email"
  }
}

Now with Argo 1.0, we slightly modify the decode function definition but everything else is the same:

struct User: Decodable {
  static func create(id: Int)(name: String)(email: String) -> User {
    return User(id: id, name: name, email: email)
  }

  static func decode(j: JSON) -> Decoded<User> {
    return User.create
      <^> j <| "id"
      <*> j <| "name"
      <*> j <| "email"
  }
}

Global decode

To decode JSON into our User model, we used to do this:

let json: AnyObject? = NSJSONSerialization...
let value: JSONValue? = json.map { JSONValue.parse($0) }
let user: User? = value.flatMap(User.decode)

To make this easier with the new Decoded<T> type, Argo 1.0 introduces a global decode function that will call parse and the model’s decode function for you.

let json: AnyObject? = NSJSONSerialization...
let user: User? = json.flatMap(decode)

You can still choose to ignore the Decoded<T> type and its error info by typing the result of the expression as a User? as we did above. If you would like to keep the error context, then type the result as Decoded<User> like so:

let json: AnyObject? = NSJSONSerialization...
let user: Decoded<User> = json.flatMap(decode)

With a Decoded<User> you can use a switch statement to handle the different success or error possibilities. You can also just print the value using println and see the error message in the console.

Some Renaming

We’ve also shortened a few names. JSONDecodable is now just Decodable and JSONValue is just JSON.

What does it all mean?

The Decoded<T> type allows you to retain detailed error information of decoding failures. The global decode function allows you to ignore the new Decoded<T> type if you choose or use it to handle decoding failures. More interestingly, both of these additions pave the way for you to implement your own return type from a decode. For instance, you can overload the global decode function to return a Result<User>. This puts power in the user’s hands to customize Argo to return values that stay consistent with the rest of the code base.

Go Forth and Decode

Thanks to everyone who helped get Argo to 1.0 through issues and PRs. We think Argo makes decoding objects from JSON easy and efficient and we’re glad you think so too! Keep your contributions coming and let’s decode for a better tomorrow together!