If you made it to the Beginning Swift workshop, you saw that we used valueForKeyPath:
to parse the JSON from iTunes like this:
var jsonError: NSError? let json = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: &jsonError) as NSDictionary if let unwrappedError = jsonError { println("json error: \(unwrappedError)") } else { self.titles = json.valueForKeyPath("feed.entry.im:name.label") as [String] }
And during the workshop, I mentioned that there are better ways to parse JSON. But what’s wrong with the above approach? Well, it works just fine when we get the response we expect. But what if we get a response we don’t expect?
The code above is missing all the type checks, which is what Swift does so well for us. It doesn’t handle unexpected responses, since there’s no checking in valueForKeyPath – it just tries to grab the value, and if it fails, it crashes. If, for example, the feed
doesn’t contain the entry
we expect, our app will crash. Below is a much safer way to parse JSON using optional binding:
func titlesFromJSON(data: NSData) -> [String] { var titles = [String]() var jsonError: NSError? if let json = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: &jsonError) as? [String: AnyObject], feed = json["feed"] as? [String: AnyObject], entries = feed["entry"] as? [[String: AnyObject]] { for entry in entries { if let name = entry["im:name"] as? [String: AnyObject], label = name["label"] as? String { titles.append(label) } } } else { if let jsonError = jsonError { println("json error: \(jsonError)") } } return titles }
So this time, when we parse our JSON, we’re using the if let
syntax to bind new constants to our optionals. And we’re checking the types on each to ensure that we have the data we expected. Let’s start with the first if let
statement:
if let json = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: &jsonError) as? [String: AnyObject], feed = json["feed"] as? [String: AnyObject], entries = feed["entry"] as? [[String: AnyObject]] { // this code is executed if the json is a dictionary AND // the "feed" is a dictionary AND // the "entry" is an array of dictionaries } else { // otherwise, this code is executed }
So in the first line, we’re turning our JSON into an object and checking to see whether it’s a dictionary with String
keys and AnyObject
values. If it’s a dictionary, the next line is executed, searching for a “feed” key. If there is one, and it’s a dictionary, the next line looks for an “entry” key. And if there’s an “entry” and it can be cast to an array of dictionaries, the if
block is executed, and we can then use the entries
array inside the if
block. Otherwise, the else
block is executed.
This optional binding continues through the code, though I’ve left out the else
blocks for the other if let
statements for brevity. You could certainly include else
blocks after each if let
if you wanted to do more extensive error handling.
The source code for this project is on GitHub – you can check out the JSONParser
class, and see both the titlesFromJSON
(the better, safer approach) and compare that to the bad-json
branch – which uses the more dangerous approach.
Happy JSON parsing!