Parsing JSON in Swift

One of the lectures I give early-on in an iOS class is an informal explanation that the Internet is less of a "series of tubes" and more of a sequence of text. Headers, html, CSS, JavaScript, rest api's, JSON, xml, etc. it's all just text in one form or another, which leads to the inevitable discussion of the Lingua Franca of the Internet: JSON.

Parsing JSON is a core component of most apps, specifically any apps that communicate with a RESTful API. For an example, say we have an app that will perform search against the Github API and then display the results to the user in a UITableView. Here's the typical process we'd need to build up in our code:

Step 1 - Make the HTTP request

Step 2 - Validate & Deserialize the response

Step 3 - Build model objects

Step 1 - Make the HTTP Request

You have a handful of choices on iOS for downloading data from the web. NSURLSession is the most powerful/flexible built-in option, and AlamoFire (Swift) or AFNetworking (ObjC) are the best options out there for a 3rd party library. For this tutorial, we'll use the most basic way of fetching data from the web, using NSData's dataWithContentsOfURL: method. This works great for quickly testing something out, just keep in mind that it isn't asynchronous and won't allow you to tweak the request headers or anything fancy like that.

var endpoint = NSURL(string: "https://api.github.com/search/repositories?q=\(searchTerm)")
var data = NSData(contentsOfURL: endpoint!)

The first line creates an NSURL that will fetch a JSON response from the Github API. The second line uses an initializer in the NSData class to load the raw response from the given URL into a new NSData object. At this point, we have no idea what (if any) data is in the newly created data object, so on to the next step where we'll validate the response and deserialize it into usable data.

Step 2 - Validate & Deserialize the response

It seems like there are more ways to parse JSON than there are Javascript frameworks, so this is my preferred way of handling things, but it's my no means the only way to do it.

if let json: NSDictionary = NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers, error: nil) as? NSDictionary {
    if let items = json["items"] as? NSArray {
        for item in items {
            // construct your model objects here
        }
    }
}

We use the if let syntax here to attempt to create an NSDictionary from the raw response data. The NSJSONSerialization class comes in handy here; it takes an NSData object, an optional error pointer and a bitmask of reading options (mutableContainers is my standard choice, but your options will depend on your use case).

The as? NSDictionary code allows you to conditionally unwrap the object, only if it can successfully be cast to an NSDictionary object. If, for example, the API returns an Array as the root level object, instead of a dictionary, the first if let in this section will fail, but not cause a crash.

After looking at the raw response, we expect the repository items to be nested under the "items" key (paste the endpoint URL from step 1 into your browser to see the raw response). Assuming the first if let succeeds, the second if let uses the newly created NSDictionary and then does the same type of operation, but this time we're trying to pull an array of repository entries from the root level object. Finally, we create a for loop to iterate over all of the items in the array.

Step 3 - Build model objects

Now we'll take that deserialized JSON goodness and make some delicious model objects. You can do this within your HTTP response block, but you'll likely want to handle it in a custom initializer in your model. Here's a basic example:

class Repo {
        var name: String
        var full_name: String
        var http_url: String
        init(json: NSDictionary) {
            self.name = json["name"] as String
            self.full_name = json["full_name"] as String
            self.http_url = json["http_url"] as String
        }
    }

Note that I'm only assigning 3 properties here. The JSON actually has 20-30 properties for each repo item, but you only need to extract and assign the parts that your app actually needs. With an initializer like this in place, you can simply create a new Repo object inside the for loop from step 2.

One thing to point out with the above example, it's not 100% safe, since the values for the keys we're passing in aren't guaranteed to be Strings. Instead, update the Repo class to use optional unwrapping and assignment. We'll also need to change the model properties to optional, since we may or may not end up assigning them a value in the initializer.

Here's what the Repo class should look like after making the safety updates:

class Repo {
        var name: String?
        var full_name: String?
        var http_url: String?
        init(json: NSDictionary) {
            if let n = json["name"] as? String {
                self.name = n
            }
            if let f = json["full_name"] as? String {
                self.full_name = f
            }
            if let h = json["http_url"] as? String {
                self.http_url = h
            }
        }
    }

From here, assuming your UITableView is setup to use an array of Repo model objects as its dataSource, you'll simply call reloadData on your UITableView, and your GUI will be updated.

This was a simple tutorial on parsing JSON, but parsing complex objects and performing validation can get a bit tricky as your app grows in size and complexity. To that end, if you wanna make your life even easier, use one of the great 3rd party JSON libraries already available for Swift, like SwiftyJSON.

Thanks for reading, and please follow up with your questions and comments below.