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.