Working With JSON In Swift With SwiftyJSON

Written by Reinder de Vries on January 30 2019 in App Development

Working With JSON In Swift With SwiftyJSON

Working with JSON can be cumbersome, but not if it’s up to SwiftyJSON! In this article, you’ll learn how to work with JSON data in Swift with the SwiftyJSON library.

Let’s get to it!

  1. What Is JSON?
  2. Parsing JSON With SwiftyJSON
  3. How SwiftyJSON Works
  4. Further Reading

What Is JSON?

Before we get started, let’s briefly discuss what JSON (“jay-son”) is. JSON stands for JavaScript Object Notation, and it’s a standardized file format that’s used to transmit data objects consisting of key-value pairs.

The data format is easily read by humans, which is convenient. You typically use JSON for async communication between clients and servers, such as an app and a webservice. For example, Twitter’s REST API outputs data as JSON.

Here’s an example of JSON:

{
    "title": "Nineteen Eighty-Four",
    "author": "George Orwell",
    "published": {
        "day": 8,
        "month": 6,
        "year": 1949,
        "iso8601": "1949-06-08T00:00:00Z"
    },
    "dewey": 823.912,
    "characters": ["Winston Smith", "Julia", "O'Brien"]
}

JSON includes 5 main data types:

  • Objects written between { and }, and separated by commas, that include nested elements as key-value pairs, separated by a colon :, of which the values can be any data type themselves, i.e. { "name": "Bob" }
  • Arrays written between [ and ], and separated by commas, that can include values of any type, i.e. [2, 3, 5, 7]
  • Strings written as string literals wrapped in double quotes, like "Arthur" (object keys are written as strings)
  • Numbers written as number literals, like 42 and 3.1415
  • Booleans written as either true or false

A special value null denotes an empty value, much like nil in Swift. And you can of course include empty strings, 0, empty arrays [] and objects {}.

It’s common to “prettify” JSON to make it easier to read, by using indentation and newlines, as seen in the example above. A JSON parser doesn’t care about those niceties, so you might as well write everything on one line.

If the structure of the JSON is broken, i.e. a } is missing, the entire JSON cannot be parsed. When you try to load the JSON in Swift, you’re likely to see an error message.

JSON has a number of sensible supersets and implementations, such as YAML, JSON-LD, MessagePack, and JSONP. When debugging JSON, it can be helpful to use an online validation and formatting tool, such as json.parser.online.fr.

Technically, any value that’s serializable can be transformed or used in JSON. This includes base64 strings, numbers, objects, custom classes and structs, and so on. If you’re looking to use well-structured objects with JSON, i.e. individual books as opposed to networking responses, check out this article about Codable: Working With JSON And Codable In Swift.

Learn how to build iOS apps

Get started with iOS 12 and Swift 4

Sign up for our iOS development course Zero to App Store and learn how to build professional iOS 12 apps with Swift 4 and Xcode 10.

Parsing JSON With SwiftyJSON

In practical iOS development, it’s common to use JSON together with webservices. APIs from Twitter, Facebook, Parse Server and even databases like MongoDB and Elasticsearch all use JSON to transmit data.

In Swift, you typically use a component called JSONSerializer to turn JSON into Swift objects and arrays. You can also use a fantastic 3rd-party library to make dealing with JSON easier, called SwiftyJSON. Before we find out how SwiftyJSON is different from JSONSerializer, let’s get into how you can use SwiftyJSON to work with JSON data.

Here’s an excerpt of the data that we’re going to work with:

{
    "num": 3,
    "query": "dystopian novels",
    "books": [
        {
            "title": "Nineteen Eighty-Four",
            "author": "George Orwell",
            "published": {
                "day": 8,
                "month": 6,
                "year": 1949,
                "iso8601": "1949-06-08T00:00:00Z"
            },
            "dewey": 823.912,
            "characters": ["Winston Smith", "Julia", "O'Brien"]
        },
        ...
    ],
    "urls": {
        "get": "/book/{id}",
        "delete": "/book/{id}/delete",
        "borrow": "/book/{id}/borrow"
    }
}

Imagine we’ve just made a network request to a book library API, and received a response with the above JSON data. How can we get to the relevant bits and pieces, using SwiftyJSON?

First, you create a JSON object. Like this:

let json = try? JSON(data: jsonData)

The above line of code parses the JSON data from a Data object called jsonData. It also converts errors to optionals with try?. If any errors occur while parsing the JSON, the constant json will be nil.

You can use a great number of JSON initializers to create a JSON object, including working with strings, Data objects, and other values. You can also use the JSON(...) initializer to construct a JSON object from a dictionary or array.

Here’s an example that shows how you can use SwiftyJSON to read JSON data:

let numberOfBooks = json["num"].intValue
print(numberOfBooks)
// Output: 3

Here’s what happens in the code:

  • With subscript syntax you access the key "num" on json, like this: json["num"]. This syntax is similar to working with dictionaries.
  • You then call the property intValue on the resulting JSON object, effectively accessing the integer value for key "num" and assigning it to numberOfBooks
  • Finally, the value is printed as output with print(...)

A JSON data structure is essentially a big tree, and we can traverse the “branches” to get to the “leaf” values that we need. Every branch is a JSON object on its own, and we can traverse it further down.

Here’s another example:

let title = json["books"][0]["title"].stringValue
print(title)
// Output: Nineteen Eighty-Four

The above line gets the title of the first book in the "books" array, using index 0, and assigns it to title. It’s then printed out.

What’s great about working with JSON this way, is that you can chain together JSON branches concisely. And every invididual branch is a full JSON object itself, that can be traversed further.

Let’s see if we can print the ISBN number for each of the 3 books in the JSON data. This will get interesting, because only 2 of 3 books have an ISBN number. How are we going to deal with that?

Here’s how:

for book in json["books"].arrayValue
{
    if  let isbn = book["isbn"].string,
        let title = book["title"].string {
        print("\(title): \(isbn)")
    }
}

And this is the output:

Brave New World: 978-0-8810-3010-5
Fahrenheit 451: 978-0-7432-4722-1

So, what’s happening here?

  • First, we use the arrayValue property on json["books"] to get an array that can be iterated with a for loop. Every array item is assigned to book, of type JSON, as we go through the array.
  • Using optional binding we check if book["isbn"].string and book["title"].string aren’t nil, and assign them to isbn and title. Subsequently, the ISBN and title are printed out.

It’s important to note here that we’re using the .string properties on the JSON objects, and not their .stringValue counterparts. There’s a distinct difference between the two, as you’ll find out in the next section…

How SwiftyJSON Works

JSON typically doesn’t have any rules that tell you how data is structured. It just has JSON syntax for objects, arrays and values – and that’s it. That means that most JSON data is unstructured and that we’ll have to traverse the JSON to get to the data we want.

If you use the JSONSerialization class to parse JSON, you’ll have to do an awful lot of checking for nil and optional casting to expected values with as?. This makes your code unneccesarily verbose and hard to read.

SwiftyJSON deals with this problem in 3 ways:

  • Every JSON object has properties for the types of values that JSON can represent. In the previous examples you’ve seen the intValue, arrayValue and stringValue properties for JSON data of which we knew that they were integers, arrays and strings.
  • Every data type has two variants, such as int and intValue. The shorter .int returns an optional of type Int?. The longer .intValue returns a non-optional Int with sensible defaults, such as 0, "" or []. As the developer, you can decide if you rather work with an optional or a default value.
  • JSON objects can be chained together to create expressive “branches”. In the previous example, you’ve seen that we effortlessly get to the title of the first book on the "books" branch. If any of the items in the chain are nil, the result is nil, much like optional chaining.

Let’s take a look at another example.

If you look carefully in the JSON data, you’ll see that the "dewey" key for the first book has a double type, and that it has a string type for the third book. Oh my, the JSON is terribly unstructured!

When we try to get the Dewey Decimal System values for the books, we run into trouble:

for book in json["books"].arrayValue
{
    if  let dewey = book["dewey"].string {
        print(dewey)
    }
}

// Output: 813.54 22

This outputs just the "dewey" value for Fahrenheit 451, even though Nineteen Eighty-Four has a value for the "dewey" key too.

Fun Fact: The Dewey Decimal System is a numbers system that’s used to locate physical books in a library. It organizes books by theme and category, so you can quickly walk over to a section or cabinet that has a book you want.

The problem here is that we’re accessing this value with .string. This will return nil when the value has a different type, and thus won’t print out the value of "dewey" for Nineteen Eighty-Four.

At this point we can choose to solve this problem in a number of ways:

  • If possible, we can correct the JSON data to always use strings for these values. This isn’t always an option, and on top of that, some JSON parsers will output number values even if the original data were strings!
  • You can change the .string to .double, and have the same problem in reverse. Now the expected value type is double, so strings will return nil.
  • You can change the .string to .stringValue and force the use of strings, which will result in an empty string for any value that’s not a string (such as the double, from the JSON data).

A fourth option exists, although it has issues of its own. Here’s how:

let dewey = String(describing: book["dewey"].rawValue)
print(dewey)

The above code uses the .rawValue property, of type Any, to just get whatever value book["dewey"] is. We can then use the String(describing:) initializer to turn this object into a string. Many Swift data types have failable initializers that can deal with different input values, for example to convert from Int to String and vice-versa.

This results in the following output:

823.912
<null>
813.54 22

The good thing is that we now have String values for the Dewey numbers of each book. The bad thing is that the second book doesn’t have a Dewey number, so it’s subsequently converted to the string <null>.

This goes to show that SwiftyJSON will go to great lengths to provide failable values with nil, or to provide sensible defaults. As a developer, you have the opportunity to explicitly ask for the types of data you expect. And it takes a lot less code than JSONSerializer!

Learn how to build iOS apps

Get started with iOS 12 and Swift 4

Sign up for our iOS development course Zero to App Store and learn how to build professional iOS 12 apps with Swift 4 and Xcode 10.

Further Reading

SwiftyJSON provides a convenient way to work with JSON data in Swift. You can clearly define the type of values you expect in the JSON data. You can also choose to work with optionals if a value is different from what you expected, or just use the sensible defaults that SwiftyJSON provides.

With a little bit of practice, you can use those JSON objects as if they’re ordinary arrays, dictionaries, or… well, whatever the heck is in your JSON!

Want to learn more? Check out these resources:

Reinder de Vries

Reinder de Vries

Reinder de Vries is a professional iOS developer. He teaches app developers how to build their own apps at LearnAppMaking.com. Since 2009 he has developed a few dozen apps for iOS, worked for global brands and lead development at several startups. When he’s not coding, he enjoys strong espresso and traveling.