How to parse JSON with Swift 3

If you’re using Swift 4, head to How to parse JSON with Swift 4 — a lot has changed since Swift 3.

Why is it so hard to parse JSON in Swift?

I can’t count the number of times I’ve seen or heard this question since Swift came out. So what’s the problem? Shouldn’t a real programming language make it super simple to work with JSON since everyone uses it everywhere?

The issue is that we’re taking weakly-typed JSON data and transforming it into strongly-typed Swift. Any number of things could go wrong, such as:

  • the JSON is just completely invalid (missing an opening brace, a closing brace, a comma, quotation marks, or something else)
  • you expect an integer in the JSON but get a string (or you expect a boolean and get an integer, or whatever…)
  • one of the values in the JSON is null
  • the structure of the JSON is different from what you expect

Swift can handle all of this just fine, but since it’s strongly typed, you need to think about these things before it compiles, rather than waiting until runtime for your app to crash. That’s a tradeoff I’m happy to make, and I’m sure the people who use my apps appreciate it, too.

Before we get into parsing JSON, remember that this is for Swift 3 — if you’re using Swift 4, you’ll want to read How to parse JSON with Swift 4 since the new JSONDecoder class changes everything.

We’ll look at how to parse a JSON array that we could use to display a list of items in a table view. More specifically, we’ll use the following JSON to display a list of blog titles in our app.

{
    "blogs": [
        {
            "id": 111,
            "url": "http://roadfiresoftware.com/blog/",
            "name": "Roadfire Software Blog"
        },
        {
            "id": 345,
            "url": "https://developer.apple.com/swift/blog/",
            "name": "Swift Developer Blog"
        }
    ]
}

Assuming we’ve made a request and received the JSON above, we just need to ask JSONSerialization to give us a JSON object, and then pull out the “blogs” and “name” keys. First, let’s take a look at the full code, then we’ll break it down and explain it in smaller pieces:

var names = [String]()

do {
    if let data = data,
        let json = try JSONSerialization.jsonObject(with: data) as? [String: Any],
        let blogs = json["blogs"] as? [[String: Any]] {
        for blog in blogs {
            if let name = blog["name"] as? String {
                names.append(name)
            }
        }
    }
} catch {
    print("Error deserializing JSON: \(error)")
}

print(names)

This gives us the following output:

["Roadfire Software Blog", "Swift Developer Blog"]

Now let’s break down what’s actually happening here.

First of all, the JSONSerialization code is in a do/catch block since jsonObject(with:) may throw an error. If you’re familiar with Objective-C, you’ll notice that we don’t pass in an NSError pointer here – instead, jsonObject(with:) is marked with throws in Swift, meaning it may throw an error. The error that we catch (which is called error by default) replaces the NSError pointer from Objective-C.

Let’s look at the first line of the conditional:

if let data = data

What in the world is that? I know, it’s weird.

We’re unwrapping the optional data — the second data in the statement, whose type is Data?. Then we’re setting it equal to a new constant called data in the first half of the statement(let data = …). This will look strange to you for maybe a few days, weeks, or months, but that’s totally normal. Just understand that it’s a way to go from a Data? type (optional Data) to a non-optional Data, keeping the same variable name. (This strange if let syntax is called optional binding, and you need to understand it if you’re writing Swift. You can learn more about it — and about optionals in general — in the free 5-Part Guide to Swift.)

Moving on:

let json = try JSONSerialization.jsonObject(with: data) as? [String: Any]

Now, we’re attempting to serialize the JSON data into an object using JSONSerialization.jsonObject(with:) and cast that to a dictionary with String keys and Any values. And the result of all that is assigned to a constant called json. Next…

let blogs = json["blogs"] as? [[String: Any]]

Here, we’re taking the "blogs" from the JSON, casting that value to an array of dictionaries ([[String: Any]]), and assigning it to a constant called blogs.

Assuming all of those let statements are successful, our if block will execute.

And finally we get to our loop which should look relatively familiar:

for blog in blogs {
    if let name = blog["name"] as? String {
        names.append(name)
    }
}

Here, we’re iterating through our array of blogs, and we know that blog is a dictionary of type [String: Any] based on the type of our blogs array. Inside the for loop, we’re doing optional binding again to grab the name of the blog as a String, then appending it to our names array.

Conclusion

I hope this helps you to start working with JSON in Swift 3 — and furthermore, I hope you see that it’s relatively simple to do using the JSONSerialization class from the built-in Foundation framework. So many people seem to want grab a third-party library to parse JSON, but for most use cases, I see very little benefit to using one to do something so simple. You don’t need a third-party library for everything.

If you want to take your JSON parsing further, I’d suggest creating model objects to represent your data, mapping your JSON to those (instead of using dictionaries everywhere), and adding some unit tests to make sure your parsing and mapping code works the way you want it to. Learn how do all of that and become a better Swift developer with Parsing JSON in Swift.