Software, Tech & Coding simplified.

Codable in Swift: JSON Parsing Made Easy [in 2022]

In Swift, Codable is a combination of Encodable and Decodable.

  • Encodable means an object is convertible from Swift to JSON (or to another external representation).
  • Decodable means an object is convertible from JSON (or other external representation) to Swift.

Practically every iOS app handles JSON data in one way or another.

Thus, learning how to encode and decode JSON is necessary.

Here is a quick example of how to:

  • Encode Swift object -> JSON.
  • Decode JSON -> Swift object
import Foundation

struct Student: Codable {
    var studentId: Int?
    var studentName: String?
}

var studentData = Data()
let student = Student(studentId: 143654, studentName: "Alice")

// Encoding = Swift object -> JSON
do {
   let data = try JSONEncoder().encode(student)
   // The JSON data is in bytes. Let's printit as a JSON string.
   if let jsonString = String(data: data, encoding: .utf8) {
       print(jsonString)
   }
   // Let's save the data to decode it back to Swift below.
   studentData = data
} catch let err {
    print("Failed to encode JSON")
}

// Decoding = JSON -> Swift object
do {
   let decodedData = try JSONDecoder().decode(Student.self, from: studentData)
   print(decodedData)
}
catch let err {
   print("Failed to decode JSON")
}

Output:

{"studentId":143654,"studentName":"Alice"}
Student(studentId: Optional(143654), studentName: Optional("Alice"))

If you are new to JSON parsing and encoding/decoding, chances are the above code caused confusion.

In this guide, we are going to take a deeper look into JSON parsing in Swift. Furthermore, we are going to learn how to use the Codable protocol to streamline the parsing process.

Introduction to Working with JSON in Swift

JSON stands for JavaScript Object Notation.

It is arguably the most commonly used text-based data format in all of computer science and programming.

JSON is a lightweight human-readable data format for exchanging data over the internet.

When it comes to sending data over the internet, you want to take as little bandwidth as possible.

This is where JSON is used.

This is a Swift tutorial, but let’s very briefly discuss JavaScript and its relation to JSON.

A JSON object is always a valid JavaScript object. This makes parsing JSON objects trivial in your JavaScript code.

To learn more about JSON and how it is used, feel free to read this article.

However, you did not come for JavaScript but Swift.

When it comes to developing iOS apps, you are going to use JSON a lot. This is because your app will most likely transmit/receive data from remote resources.

Unfortunately, a JSON object is not a valid Swift object (even though it reminds a Swift dictionary).

To deal with JSON objects in your app, you need to learn two important concepts:

  • Decoding a JSON object to a Swift object.
  • Encoding a Swift object to a JSON object.

This is where the Codable protocol comes in.

Next, we are going to take a deeper look into the Codable protocol in Swift.

Codable in Swift

In Swift, the Codable protocol allows you to:

  • Decode JSON to Swift object.
  • Encode Swift object to JSON.

As a matter of fact, Codable is a combination of two protocols, Decodable and Encodable.

Notice that you are not limited to using JSON with Codable.

By definition, it allows you to convert between Swift objects and an external representation.

For example, you can convert a .plist to a Swift structure.

However, in this tutorial, we solely focus on JSON.

Understanding Codable in Swift: An In-Depth Look

In Swift, codability means being able to convert a Swift object to JSON and vice versa.

To make a Swift object codable, you have to make the data type conform to the Codable protocol.

struct User: Codable { ... }

If you only want to decode your objects, you can use the Decodable protocol.

struct User: Decodable { ... }

Similar if you only want to encode objects, use the Encodable protocol.

struct User: Encodable { ... }

However, in this guide, we are going to use the Codable protocol, as we want to do both.

The majority of Swift’s built-in datatypes are codable by nature.

These include:

  • Int
  • String
  • Bool
  • Dictionary
  • Array

Notice that the arrays and dictionaries need to consist of codable objects.

Otherwise, they are not codable.

Arrays and dictionaries are central concepts when it comes to working with JSON.

JSON format looks a lot like a dictionary in Swift.

For instance, here is an example JSON object:

[
    {
        "first_name": "Steve",
        "last_name": "Jobs"
    },
    {
        "first_name": "Elon",
        "last_name": "Musk"
    }
]

As a comparison, here is the same object in Swift:

[
    [
        "first_name": "Steve",
        "last_name": "Jobs"
    ],
    [
        "first_name": "Elon",
        "last_name": "Musk"
    ]
]

As you can see, JSON objects and Swift dictionaries look almost identical.

It is common you want to convert a dictionary to JSON and vice versa.

Anyway, now you have a conceptual understanding of what codability means.

Next, let’s create a custom type that we want to be able to convert to JSON.

Defining a Codable Struct in Swift

Let’s say your app is interacting with a remote database that stores the data of students of a university.

When you request student data from the DB, you get back student objects as JSON.

An example of a student JSON object looks like this:

{ 
   "studentId": 543982,
   "studentName": "Alice" 
}

When you get back a JSON string like this, there is not much you can do with it in your code.

To be able to work with this data, you need to convert it to a Swift object.

To do this, let’s create a structure Student. This is the model that corresponds to the JSON data.

struct Student {
    var studentId: Int?
    var studentName: String?
}

Now you have the Student structure in place.

However, there is no way to decode the JSON data to a Student object just yet.

This is because, by default, the custom types like Student is not decodable/encodable.

To make the Student structure support JSON encoding/decoding, make it conform to the Codable protocol:

struct Student: Codable {
    var studentId: Int?
    var studentName: String?
}

Decoding JSON to Swift

Now you are ready to convert student JSON data into a Swift object.

The JSON data gets converted to a Swift object

To keep it simple, you are not going to perform an actual network request to fetch JSON data.

Instead, you are going to mimic a database request by crafting the JSON yourself.

Here is the student data as a JSON object you would receive from a real database:

let data = """
    {
        "studentId": 548293,
        "studentName": "Alice"
    }
"""
let studentJSON = Data(data.utf8)

Now you can use the built-in JSONDecoder to convert this JSON data to a Student object.

The JSON data could be invalid. Thus you need to handle possible errors in the decoding process:

do {
    let decoded = try JSONDecoder().decode(Student.self, from: studentJSON)
    print(decoded)
} catch {
    print("Failed to decode JSON")
}

Output:

Student(studentId: Optional(548293), studentName: Optional("Alice"))

As you can see, you have successfully converted a JSON object to a Student object in your code. Now you can use this Student object in your code any way you want.

To understand where the magic happens, take a look at this line in the above piece of code:

try JSONDecoder().decode(Student.self, from: studentJSON)

This line of code tells a JSONDecoder() object to convert a JSON object to a Student object in our Swift code.

Here is all the code we have written so far for your convenience:

import Foundation

struct Student: Codable {
    var studentId: Int?
    var studentName: String?
}

// Student data as a JSON object from the DB
let data = """
    {
        "studentId": 548293,
        "studentName": "Alice"
    }
"""
let studentJSON = Data(data.utf8)

do {
    let decoded = try JSONDecoder().decode(Student.self, from: studentJSON)
    print(decoded)
} catch {
    print("Failed to decode JSON")
}

Now you have seen an example of how to decode JSON data to a Swift object.

Now it is time to do the exact opposite.

Encoding Swift Objects to JSON

More often than not, your app not only receives JSON data from a database but also sends it there.

In other words, you need to be able to convert Swift objects to JSON format.

This process is called encoding.

Let’s continue with the Student example.

You have specified a Student struct for representing students in your app.

struct Student: Codable {
    var studentId: Int?
    var studentName: String?
}

Our task is to prepare Student objects in JSON format to send them over to the database.

Because the Student structure conforms to the Codable protocol, it can easily be converted to JSON.

To convert a Student object to JSON, you need to:

  • Create a Student object.
  • Use the built-in JSONEncoder() to encode the object into JSON.

There is always a chance that the encoding process fails. Thus, you need to do some error handling to make sure not to crash the app.

Here is what encoding a Student object to JSON looks like:

let student = Student(studentId: 732893, studentName: "Bob")

do {
   let data = try JSONEncoder().encode(student)
   // Print the encoded JSON data
   if let jsonString = String(data: data, encoding: .utf8) {
       print(jsonString)
   }
} catch let err {
    print("Failed to encode JSON")
}

Output:

{"studentName":"Bob","studentId":732893}

That works!

To see where the magic happens, take a look at this piece of code:

try JSONEncoder().encode(student)

Here you tell the JSONEncoder() object to turn a student object into JSON.

Now you understand how to convert objects between JSON and Swift using Codable data types and JSON decoders and encoders.

A Quick Recap

JSON is a text-based data format. It is used to transmit data over the internet.

When you request data from a remote resource, you commonly get JSON data back.

To use this JSON data in your iOS app, you need to be able to convert it to a Swift object.

Converting JSON to Swift is called decoding. This is also commonly referred to as parsing.

Likewise, you commonly have to send data from your app to a server. To do this, you have to convert your Swift objects to JSON.

This is called encoding.

To decode or encode data, your objects must conform to the Codable protocol.

Then you can use JSONDecoder() or JSONEncoder() objects to decode/encode the data.

Next, let’s deal with a more realistic situation where the JSON keys do not match the desired property names.

JSON Parsing with Custom Property Names

Parsing JSON data is not always straightforward.

Sometimes the JSON keys have different names than the object properties.

For instance, your JSON data could have keys with the names user_name or user_location. On the other hand, your program code uses userName and userLocation.

This is problematic because the JSON decoder has no idea these names are related to one another.

At this point, you might consider changing the names of your model in Swift to match the keys in the JSON data.

But that is a bad idea.

Sometimes JSON keys are named in a rather verbose manner or against the naming conventions of Swift.

Thus, you commonly do not want to name your Swift object properties the same way as the JSON data.

This is where you can specify a CodingKeys mapping.

CodingKeys Mapping in Swift

The CodingKey mapping maps the JSON names to the correct property names.

This helps the JSONDecoder object to do its job.

To make any sense of this, you need to see an example.

Let’s continue with the Student example from the previous chapters.

Let’s assume the JSON data consists of snake case keys like this:

{ 
   "student_id": 543982, 
   "student_name": "Alice" 
}

Now the keys student_id and student_name differ from the property names studentId and studentName in your code:

struct Student: Codable {
    var studentId: Int?
    var studentName: String?
}

This is a problem.

If you try to decode the JSON object into a Student object, it would not produce the desired results.

This is because the property names and the JSON keys do not match.

This is where you can use the CodingKeys mapping.

The idea is to provide the following information to the JSONDecoder object:

student_id --> studentId
student_name --> studentName

To create this mapping, add a CodingKeys enumeration into the Student structure:

struct Student: Codable {
    var studentId: Int?
    var studentName: String?

    enum CodingKeys: String, CodingKey {
        case studentId = "student_id"
        case studentName = "student_name"
    }
}

After this, you can use the exact same JSON decoding code you saw in the previous example:

// Student data as a JSON object from the DB
let data = """
    {
        "student_id": 548293,
        "student_name": "Alice"
    }
"""
let studentJSON = Data(data.utf8)

do {
    let decoded = try JSONDecoder().decode(Student.self, from: studentJSON)
    print(decoded)
} catch {
    print("Failed to decode JSON")
}

Now the JSON data is parsed correctly:

Student(studentId: Optional(548293), studentName: Optional("Alice"))

Here is the full code for your convenience:

import Foundation

struct Student: Codable {
    var studentId: Int?
    var studentName: String?
    
    // Map the JSON keys to the correct property names
    enum CodingKeys: String, CodingKey {
        case studentId = "student_id"
        case studentName = "student_name"
    }
}

// Student data as a JSON object from the DB
let data = """
    {
        "student_id": 548293,
        "student_name": "Alice"
    }
"""
let studentJSON = Data(data.utf8)

do {
    let decoded = try JSONDecoder().decode(Student.self, from: studentJSON)
    print(decoded)
} catch {
    print("Failed to decode JSON")
}

Now you understand how to create custom mappings between the JSON keys and Swift property names.

Next, let’s see another way to do this particular mapping by learning about decoding strategies.

Parse JSON Snake Case to Camel Case in Swift

When decoding JSON data, you can specify a separate decoding strategy to parse keys from snake case to camel case.

JSON data commonly consists of keys in the snake case.

On the other hand, your Swift code usually uses the camel case.

In other words, converting the JSON data from camel case to snake case is commonplace.

Thus, there is a simple built-in mechanism for doing it.

In the previous example, you saw how to specify a mapping to map the keys to the property names.

However, when converting between a snake case and a camel case, you do not need to do it that way.

Instead, you can specify a key decoding strategy to the JSONDecoder object.

To do this:

  • Specify a JSONDecoder() object on a separate line.
  • Set the .keyDecodingStrategy parameter to .convertFromSnakeCase.

For example, let’s repeat the example in the previous chapter using this key decoding strategy:

import Foundation

struct Student: Codable {
    var studentId: Int?
    var studentName: String?
}

let data = """
    {
        "student_id": 548293,
        "student_name": "Alice"
    }
"""
let studentJSON = Data(data.utf8)

let decoder = JSONDecoder()
// Specify the decoding strategy:
decoder.keyDecodingStrategy = .convertFromSnakeCase

do {
    let decoded = try decoder.decode(Student.self, from: studentJSON)
    print(decoded)
} catch {
    print("Failed to decode JSON")
}

As you can see, this now correctly maps the snake case keys to camel case property names.

Student(studentId: Optional(548293), studentName: Optional("Alice"))

Now you understand how to map JSON keys to property names when the names differ from one another.

Next, let’s take a look at how to parse date strings in the JSON data.

How to Parse JSON Dates in Swift

To parse JSON date to a Date object in Swift, you need to specify a date decoding strategy into the JSONDecoder object.

Storing a timestamp on a database is extremely common.

When your app parses a JSON response with a date, it should know how to turn it into a Date object in Swift.

This is because it is tedious to work with dates as strings.

This is when you can specify a date decoding strategy to the JSONDecoder object. This way you tell it how to handle timestamps in the JSON data.

Let’s see an example.

This time we include a timestamp to the student data.

Here is what a student JSON object looks like in the database:

{ 
   "student_id": 543982,
   "student_name": "Alice",
   "start_date": "2021-08-31T09:00:00Z"
}

To decode this JSON data into a Swift object, you can almost reuse the code from the previous example.

The only extra is to also build a DateFormatter object and attach it to the JSONDecoder.

Here is how it looks in code:

import Foundation

struct Student: Codable {
    var studentId: Int?
    var studentName: String?
    var startDate: Date?
}

let data = """
    {
        "student_id": 548293,
        "student_name": "Alice",
        "start_date": "2021-08-31T09:00:00Z"
    }
"""
let studentJSON = Data(data.utf8)

let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase

// Convert JSON dates to Date objects with this strategy
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
decoder.dateDecodingStrategy = .formatted(dateFormatter)

do {
    let decoded = try decoder.decode(Student.self, from: studentJSON)
    print(decoded)
} catch {
    print("Failed to decode JSON")
}

Output:

Student(studentId: Optional(548293), studentName: Optional("Alice"), startDate: Optional(2021-08-31 09:00:00 +0000))

To learn more about date formatting in Swift, check this guide.

Now you know the basics of parsing dates from JSON as well.

Before wrapping up, let’s see a complete example of parsing JSON from a real remote database in Swift.

How to Fetch Data from an API in Swift

Let’s apply what you have learned today in fetching data from a real database and decoding the data for later use.

In the previous example, the JSON data was not fetched from a server. Instead, you used the self-produced JSON that you wrote to the program to keep it simple.

However, to make this guide complete, you have to perform a real database query and parse real JSON data from a server.

To fetch data from an API in Swift:

  1. Create a model, such as a structure, to represent the data.
  2. Fetch the data using URLSession.shared.dataTask.
  3. Decode the JSON response using the model you created.

This is the process in a nutshell.

In this example, you are going to use the placeholder JSON data from jsonplaceholder.typicode.com/todos.

This is a real remote database that consists of to-do items.

You can use their free API to fetch the items to your app.

The JSON data on the server looks like this:

[
  {
    "userId": 1,
    "id": 1,
    "title": "delectus aut autem",
    "completed": false
  },
  {
    "userId": 1,
    "id": 2,
    "title": "quis ut nam facilis et officia qui",
    "completed": false
  },
  {
    "userId": 1,
    "id": 3,
    "title": "fugiat veniam minus",
    "completed": false
  },
  {
    "userId": 1,
    "id": 4,
    "title": "et porro tempora",
    "completed": true
  },
  .
  .
  .
]

It is a JSON object that is an array of to-do items.

To convert these to-do items to Swift objects, you need to have a codable to-do model in your code.

Let’s create one:

struct ToDo: Codable {
    let userId: Int
    let id: Int
    let title: String
    let completed: Bool
}

Now the goal is to convert the JSON to-do items into real ToDo objects in your code.

Please, do not pay too much attention to the implementation details of the network requesting function. The only relevant part is the one where you convert the JSON response to a list of ToDo objects.

To access the to-do items in the server, use the URLSession class.

More specifically, let’s extend the URLSession class with a function that fetches the items behind a URL.

Here is what it looks like in code:

import Foundation
import FoundationNetworking

// The ToDo structure for the to-do elements
struct ToDo: Codable {
    let userId: Int
    let id: Int
    let title: String
    let completed: Bool
}

// A function to fetch the to-do elements behind a URL:
extension URLSession {
    func fetch(from url: URL, completion: @escaping (Result<[ToDo], Error>) -> Void) {
        self.dataTask(with: url) { (data, res, err) in
            if let err = err {
                completion(.failure(err))
            }

            if let data = data {
                do {
                    // Convert the JSON data into an array of ToDo objects:
                    let todos = try JSONDecoder().decode([ToDo].self, from: data)
                    completion(.success(todos))
                } catch let err {
                    completion(.failure(err))
                }
            }
        }.resume()
    }
}

Now you can test this piece of code to see how it fetches the to-do items from the remote database:

let url = URL(string: "https://jsonplaceholder.typicode.com/todos")!

URLSession.shared.fetch(from: url) { data in
    switch data {
    case .success(let todos):
        print("Here is the list of ToDo items:")
        print(todos)
    case .failure(let err):
        print("Fetching failed with: \(err)")
    }
}

As a result, you should see a long list of to-do items printed into the console.

Now you could continue using these ToDo objects in your app any way you want. For instance, you could build an interface for showing them nicely.

To keep it in scope, we are not going to do that in this guide.

Anyway, now you have a good understanding of how to decode and encode JSON data in your app.

Conclusion

Today you learned how to use the Codable protocol to parse JSON in Swift.

To recap, the built-in Codable protocol makes it possible to:

  • Convert JSON to Swift (decoding).
  • Convert Swift to JSON (encoding).

In reality, the Codable protocol is just a combination of Decodable and Encodable protocols.

If you only want to decode JSON data, then you can make your custom type Decodable. And similar, if you only need to encode data, you can make the custom type Encodable.

Usually, the JSON data contains:

  • Keys in snake case, such as student_id.
  • Dates or timestamps.

For this, you can specify the .keyDecodingStrategy and .dateDecodingStrategy properties of the JSONDataFormatter.

If the JSON keys use more complex naming, you can always specify a CodingKey mapping to map the JSON keys to Swift properties with the right names.

Thanks for reading.

Happy coding!

Further Reading

50 Swift Interview Questions

Share

Share on twitter
Share on linkedin
Share on facebook
Share on pinterest
Share on email