Codable is a beautiful tool provided by Apple to perform the serialization and deserialization of a value in a (mostly) declarative way.

With Codable you can

convert a JSON into an instance of some Type

generate a JSON using an instance of some Type

In this article for beginners, you are going to learn how to use Codable in 5 simple scenarios:

Decoding a simple JSON Adding a field of type Double Mapping a snake_case JSON to a camelCase swift Type Decoding a list of elements Optional values

Scenario 1: decoding a simple JSON

Let’s start with a very easy scenario, you have a JSON like this one

let json = """

{

"name": "Enterprise D",

"captain": "Jean Luc Picard"

}

"""

and you have a struct like this one

struct Ship: Codable {

let name: String

let captain: String

}

You can generate an instance of Ship simply writing

guard let data = json.data(using: String.Encoding.utf8) else { fatalError("☠️") } do {

let ship = try JSONDecoder().decode(Ship.self, from: data)

print(ship)

} catch {

print(error)

}

The result will be

Ship(name: “Enterprise D”, captain: “Jean Luc Picard”)

How does it work?

The automatic decoding mechanism works because:

Ship conforms to Codable the JSON key names and the Ship struct property names do match the JSON key types and the Ship struct property matched match

This is a great example of declarative programming, we have just described how the Ship type is and left to Codable all the boring stuff.

Scenario 2: adding a field of type Double

Ok that was easy, now let’s say our JSON has another property of type Double

let json = """

{

"name": "Enterprise D",

"captain": "Jean Luc Picard",

"max_warp_speed": 9.6

}

"""

Let’s update our struct

struct Ship: Codable {

let name: String

let captain: String

let max_warp_speed: Double

}

And run the decoding instruction

guard let data = json.data(using: String.Encoding.utf8) else { fatalError("☠️") } do {

let ship = try JSONDecoder().decode(Ship.self, from: data)

print(ship)

} catch {

print(error)

}

Again we get a new instance of Ship

Ship(name: “Enterprise D”, captain: “Jean Luc Picard”, max_warp_speed: 9.6)

Scenario 3: mapping a snake_case JSON to a camelCase Struct

The max_warp_speed property used in the previous scenario is not very “Swifty” right? We should use camelCase notation

struct Ship: Codable {

let name: String

let captain: String

let maxWarpSpeed: Double

}

But now the name of the property maxWarpSpeed no longer matches the key in the JSON max_warp_speed.

Good news is that we can tell Codable to automatically convert the names from snake_case_notation (used in the JSON) to camelCase (used in the Struct).

struct Ship: Codable {

let name: String

let captain: String

let maxWarpSpeed: Double

} guard let data = json.data(using: String.Encoding.utf8) else { fatalError("☠️") } do {

let decoder = JSONDecoder()

decoder.keyDecodingStrategy = .convertFromSnakeCase // 👈

let ship = try decoder.decode(Ship.self, from: data)

print(ship)

} catch {

print(error)

}

Scenario 4: a list of elements

Let’s consider now a JSON with a list of elements

let json = """

[

{

"name": "Enterprise D",

"captain": "Jean Luc Picard",

"max_warp_speed": 9.6

},

{

"name": "Defiant",

"captain": "Benjamin Sisko",

"max_warp_speed": 9.2

},

{

"name": "Voyager",

"captain": "Captain Kathryn Janeway",

"max_warp_speed": 9.975

}

]

"""

We can decode this JSON into an array of Ship(s).

struct Ship: Codable {

let name: String

let captain: String

let maxWarpSpeed: Double

} guard let data = json.data(using: String.Encoding.utf8) else { fatalError("☠️") } do {

let decoder = JSONDecoder()

decoder.keyDecodingStrategy = .convertFromSnakeCase

let ships = try decoder.decode([Ship].self, from: data) // 👈

print(ships)

} catch {

print(error)

}

We replaced this line

let ship = try decoder.decode(Ship.self, from: data)

with this line

let ships = try decoder.decode([Ship].self, from: data)

Scenario 5: optional values

Now we want to add a position key to our JSON

let json = """

[

{

"name": "Enterprise D",

"captain": "Jean Luc Picard",

"max_warp_speed": 9.6,

"position": "Alpha quadrant"

},

{

"name": "Defiant",

"captain": "Benjamin Sisko",

"max_warp_speed": 9.2,

"position": "Alpha quadrant"

},

{

"name": "Voyager",

"captain": "Captain Kathryn Janeway",

"max_warp_speed": 9.975,

"position": null

}

]

"""

As you can see the position property is populated only for the first 2 elements (we don’t know Voyager's position).

Let’s update our Struct

struct Ship: Codable {

let name: String

let captain: String

let maxWarpSpeed: Double

let position: String

}

And let’s run the code again

guard let data = json.data(using: String.Encoding.utf8) else { fatalError("☠️") } do {

let decoder = JSONDecoder()

decoder.keyDecodingStrategy = .convertFromSnakeCase

let ships = try decoder.decode([Ship].self, from: data)

print(ships)

} catch {

print(error)

}

As a result we get this error

valueNotFound(Swift.String, Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: “Index 2”, intValue: 2), CodingKeys(stringValue: “position”, intValue: nil)], debugDescription: “Expected String value but found null instead.”, underlyingError: nil))

Codable is telling us that position is defined as mandatory in our struct but has not been found in the 3rd element of the JSON.

We can fix it making “position” an optional value in our struct

struct Ship: Codable {

let name: String

let captain: String

let maxWarpSpeed: Double

let position: String? // 👈

}

Let try again, the problem is now fixed and as a result, we get our ships array properly populated.

Conclusion

In this article, you learned the basic operations you can do with the Codable protocol. However, this tool is way more powerful and can do many more things. I will cover that in a future article.