How To: Working With JSON And Codable In Swift

Written by Reinder de Vries on July 4 2018 in App Development

How To: Working With JSON And Codable In Swift

Codable is a powerful protocol for Swift 4+. You use it to encode and decode data formats, such as JSON, to native Swift objects. You can map Swift objects to JSON data, simply by adopting the Codable protocol. How? I’ll show you in this article!

As a pragmatic iOS developer you’ll come across JSON sooner than later. Every webservice, from Facebook to Foursquare, uses JSON to get data into your app. How do you effectively turn that JSON into Swift objects?

In this article you’ll learn how to work with JSON objects in Swift, by using the Codable protocol. We’ll go into encoding and decoding with JSONEncoder and JSONDecoder, and I’ll show you how to map between JSON and Swift structs.

Ready? Let’s go.

  1. Why Is Encoding And Decoding Important?
  2. How To: The Codable Protocol
  3. Decoding JSON With Codable
  4. Encoding Objects As JSON With Codable
  5. Further Reading

Why Is Encoding And Decoding Important?

So, what problem does Codable actually solve? Let’s take a look at an example.

Imagine you’re building a recipe app. The app shows various recipes in a list, including the ingredients, instructions and basic information about food. You get the data for the app from a webservice, and their API. This API uses the JSON data format.

JSON is a text-based data format that many webservices use, including APIs from Twitter, Facebook, Foursquare etc.

Here’s an example:

{
    "name": "Spaghetti Bolognese",
    "author": "Reinder's Cooking Corner",
    "url": "https://cookingcorner.com/spaghetti-bolognese",
    "yield": 4,
    "ingredients": ["cheese", "love", "spaghetti", "beef", "tomatoes", "garlic"],
    "instructions": "Cook spaghetti, fry beef and garlic, add tomatoes, add love, eat!"
}

JSON objects are wrapped in squiggly brackets { and }, arrays are wrapped in square brackets [ and ]. Property names are strings, wrapped in quotes. And values can be strings, numbers (no quotes), arrays or other objects.

But that’s not what’s interesting here… what’s more important is what JSON means for connecting apps and webservices!

See, one of the powers of the internet is the ability to connect many computers in a network. These computers communicate with each other by using various protocols, such as TCP/IP, HTTP and SSH. These protocols are based on mutual agreements. The computers are able to communicate because they agree on the “language” they use to talk to each other.

Websites, for instance, are built on top of those protocols. When your browser requested this webpage from my webserver, it got a response back that uses the HTML format. The HTML describes the webpage, and your browser renders it, so your eyes can see and read the page.

What’s that got to do with JSON? JSON is an agreed upon format for webservices, APIs and apps. It’s used throughout the web, apps, and online services, because the format is simple and flexible.

And JSON has one superb capability: you can encode any data format in JSON, and decode JSON back to any data format. This encoding and decoding process is what makes JSON so powerful.

Other formats, such as XML, can also be encoded and decoded. JSON is the most popular format for webservices and apps.

You can take your Swift String, Int, Double, URL, Date, Data, Array and Dictionary values, and encode them as JSON. You then send them to the webservice, which decodes the values into a native format that it understands. Similarly, the webservice sends data encoded as JSON to your app, and you decode the data to native types such as String and Array.

When your recipe app receives the JSON (see above), it can then be decoded into a Swift struct, like this one:

struct Recipe {
    var name:String
    var author:String
    var url:URL
    var yield:Int
    var ingredients:[String]
    var instructions:String
}

Where Codable comes in, is encoding and decoding data formats from their native types to other formats, such as JSON. Let’s move on to see how!

Learn how to build iOS apps

Get started with iOS 11 and Swift 4

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

How To: The Codable Protocol

Using JSON before Swift 4 was a bit of a PITA. You’d have to serialize the JSON yourself, and then type cast every property of the JSON to the right Swift type.

Like this:

let json = try? JSONSerialization.jsonObject(with: data, options: [])

if let recipe = json as? [String: Any] {
    if let yield = recipe["yield"] as? Int {
        recipeObject.yield = yield
    }
}

It’s extremely verbose, and it’s hard to properly respond to potential errors and type discrepancies. Even though it works, it’s not ideal.

Libraries like SwiftyJSON make working with JSON a lot easier, but you still need to map the JSON data to its respective Swift objects and properties.

With Swift 4 you can now use the Codable protocol. Your Swift struct or class merely has to adopt that protocol, and you get JSON encoding and decoding for free.

The Codable protocol is a composition of two protocols, Decodable and Encodable. Both protocols are pretty minimal, and only define the functions init(from: Decoder) and encode(to: Encoder) respectively.

The real magic happens in the protocols Decoder and Encoder, and the JSONDecoder class we’ll use next, uses those protocols.

In essence, the JSONDecoder class contains code to convert the JSON format to a key-value container. Thanks to composition, you can create your own encoder and decoder for any imaginable format.

Let’s take a look at an example.

First, we’re creating a struct called User. Like this:

struct User:Codable {
    var first_name:String
    var last_name:String
    var country:String
}

The User struct has three simple properties of type String, and conforms to the Codable protocol.

Then, let’s write a bit of JSON. This is the JSON we’ll work with:

{
    "first_name": "John", 
    "last_name": "Doe", 
    "country": "United Kingdom"
}

JSON data typically enters your app as the response of a webservice request, but for this example we simply put the JSON in a String. Like this:

let jsonString = """
{
    "first_name": "John",
    "last_name": "Doe",
    "country": "United Kingdom"
}
"""

Note: The above code uses the triple quote """ to create multi-line strings. Neat!

What we’re doing next, is decoding the JSON and turning it into a User object. Like this:

let jsonData = jsonString.data(using: .utf8)!
let user = try! JSONDecoder().decode(User.self, from: jsonData)
print(user.last_name)
// Output: Doe

Here’s what happens:

  • First, you turn jsonString into a Data object by calling the data(using:) function on the string. This is a necessary intermediate step.
  • Then, you create a JSONDecoder object and immediately call the decode(_:from:) function on it. This turns the jsonData into an object of type User, by decoding the JSON.
  • Finally, you print out the last name of the user with print(user.last_name).

Easy, right? You’ve essentially “mapped” the JSON object to a Swift struct, and decoded the JSON format to a native object Swift can work with. Awesome!

Decoding JSON With Codable

Let’s take another look at the example from the previous section, and expand it. Here’s the JSON we’re working with:

let jsonString = """
{
    "first_name": "John",
    "last_name": "Doe",
    "country": "United Kingdom"
}
"""

We then turn that into a Data object. Like this:

let jsonData = jsonString.data(using: .utf8)!

This is a necessary intermediate step. Instead of representing the JSON as a string, we now store the JSON as a native Data object.

If you look closely, you’ll see that the above code uses force unwrapping to work with the optional return value from data(using:). Let’s unwrap that optional more elegantly!

if let jsonData = jsonString.data(using: .utf8) 
{
    // Use jsonData
} else {
    // Respond to error  
}

With the above code we can respond to errors when data(using:) returns nil. You could for instance show an error message, or silently let the task fail and save diagnostic information in a log.

Next, we’re creating a new JSONDecoder object. Like this:

let decoder = JSONDecoder()

We then use that decoder to decode the JSON data. Like this:

let user = try! decoder.decode(User.self, from: jsonData)

However, the decode(_:from:) function can throw errors. The above code crashes whenever that happens. Again, we want to respond to any errors that might happen. Like this:

do {
    let user = try decoder.decode(User.self, from: jsonData)
    print(user.last_name)
} catch {
    print(error.localizedDescription)
}

And the entire code block now looks like the following. See how that’s different?

if let jsonData = jsonString.data(using: .utf8)
{
    let decoder = JSONDecoder()

    do {
        let user = try decoder.decode(User.self, from: jsonData)
        print(user.last_name)
    } catch {
        print(error.localizedDescription)
    }
}

Never silence errors. Catch the error and respond to it, either with UI/UX, by retrying the task, or by logging, crashing and fixing it.

What if our JSON properties, like first_name, are different than the Swift struct properties? That’s where CodingKeys comes in.

Every class or struct that conforms to Codable can declare a special nested enumeration called CodingKeys. You use it to define properties that should be encoded and decoded, including their names.

Let’s take a look at an example. In the User struct below, we’ve changed the property names from snake_case to camelCase.

struct User:Codable 
{
    var firstName:String
    var lastName:String
    var country:String

    enum CodingKeys: String, CodingKey {
        case firstName = "first_name"
        case lastName = "last_name"
        case country
    }
}

When you use the above struct with the examples in the previous sections, you’ll see that you can use a User object with the new property names. Like this:

print(user.firstName)
// Output: John

print(user.country)
// Output: United Kingdom

The CodingKeys enumeration is self-explanatory. It simply maps cases to properties, and uses the string values to select the right property names in the JSON data.

Encoding Objects As JSON With Codable

Can we also encode objects as JSON? Yes, why not! Like this:

var user = User()
user.firstName = "Bob"
user.lastName = "and Alice"
user.country = "Cryptoland"

let jsonData = try! JSONEncoder().encode(user)
let jsonString = String(data: jsonData, encoding: .utf8)!
print(jsonString)

And the output is:

{"country":"Cryptoland","first_name":"Bob","last_name":"and Alice"}

What happens there?

  • First, you create a User object and assign some values to its properties.
  • Then, you use encode(_:) to encode the user object to a JSON Data object.
  • Finally, you convert the Data object to String and print it out.

If you look closely, you’ll see that encoding follows the same steps as decoding, except in reverse.

Can we expand the example to deal with errors? Yes! Like this:

let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted

do {
    let jsonData = try encoder.encode(user)

    if let jsonString = String(data: jsonData, encoding: .utf8) {
        print(jsonString)
    }
} catch {
    print(error.localizedDescription)
}

In the above example, we’re also using the outputFormatting property of the encoder to “pretty print” the JSON data. This adds spaces, tabs and newlines to make the JSON string easier to read. Like this:

{
    "country" : "Cryptoland",
    "first_name" : "Bob",
    "last_name" : "and Alice"
}

Awesome!

Learn how to build iOS apps

Get started with iOS 11 and Swift 4

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

Further Reading

And that’s all there is to it! You now know:

  • How to use Codable to create objects that can be encoded and decoded
  • How to use JSONDecoder and JSONEncoder to encode and decode JSON objects and their Swift counterparts
  • What encoding and decoding is for, and why it’s important in everyday iOS development

Want to learn more? Check out these resources:

Got a question? Leave a comment below!

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.

Leave a Reply

Required to post: Your real name and email address, and a pleasant demeanor. Your email address will not be published. Markdown is supported.