You can easily parse JSON data in Swift by creating a Decodable
type that matches the structure of a JSON object.
However, we often need to decode JSON data that does not match the model types of our apps. In those cases, we need to control the decoding using coding keys.
- This article is part of the Parsing JSON in Swift series.
FREE GUIDE: Parsing JSON in Swift - The Ultimate Cheat Sheet
Learn the fundamentals of JSON parsing in Swift, advanced decoding techniques, and tasks for app development
DOWNLOAD THE FREE GUIDETable of contents
- How CodingKeys work and when you need them
- Renaming the fields of a JSON object to follow Swift’s conventions
- Selecting the stored properties of a decodable Swift type that must be decoded
- Adding extra logic to the decoding process through a decoding initializer
- Flattening nested JSON object into a single Swift model type
How CodingKeys work and when you need them
At its simplest, decoding JSON data in Swift does not require coding keys. All you need is a Decodable
Swift type that matches the data you want to decode.
let json = """
{
"name": "Rembrandt Harmenszoon van Rijn",
"birthplace": "Leiden, Dutch Republic",
"profession": "Painter"
}
""".data(using: .utf8)!
struct Artist: Decodable {
let name: String
let birthplace: String
let profession: String
}
let artist = try JSONDecoder().decode(Artist.self, from: json)
However, that is often not practical.
Swift types should be modeled after your app’s business logic and not merely follow the data formats received by your app.
That means you often need to control the decoding of JSON data to add logic fine-tuned to your app’s data representation.
Renaming the fields of a JSON object to follow Swift’s conventions
The most common use for coding keys is to rename the fields of JSON data into stored properties that follow the Swift API Design Guidelines.
Since JSON data usually comes from an Internet server, it often follows the naming conventions of web development.
One of these conventions is using snake case instead of camel case, which is Swift’s naming convention.
If you only need to change the naming convention, you do not need to use coding keys. Setting the keyDecodingStrategy
property of the JSONDecoder
to .convertFromSnakeCase
is enough.
let json = """
{
"first_name": "Rembrandt",
"last_name": "van Rijn",
"profession": "Painter"
}
""".data(using: .utf8)!
struct Artist: Decodable {
let firstName: String
let lastName: String
let profession: String
}
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let artist = try decoder.decode(Artist.self, from: json)
Coding keys are required to completely rename some or all of a JSON object’s fields.
You must name your coding keys following your type’s property names while using a String
raw value for the corresponding field in the JSON data.
let json = """
{
"first_name": "Rembrandt",
"last_name": "van Rijn",
"profession": "Painter"
}
""".data(using: .utf8)!
struct Artist: Decodable {
let name: String
let surname: String
let profession: String
enum CodingKeys: String, CodingKey {
case name = "first_name"
case surname = "last_name"
case profession
}
}
let artist = try JSONDecoder().decode(Artist.self, from: json)
Selecting the stored properties of a decodable Swift type that must be decoded
Another everyday use for coding keys is to select which properties must be encoded or decoded. Your Swift types often have extra properties used by your app’s logic that are not present in the JSON data.
The JSONDecoder
class tries to decode all the properties of a Decodable
type. If any of those properties are absent in the JSON data, it will throw an error.
Using coding keys, you can indicate the specific properties to be decoded.
let json = """
{
"name": "Rembrandt Harmenszoon van Rijn",
"profession": "Painter"
}
""".data(using: .utf8)!
struct Artist: Decodable {
let name: String
let profession: String
var viewCount: Int = 0
var isFavorite: Bool = false
enum CodingKeys: CodingKey {
case name
case profession
}
}
let artist = try JSONDecoder().decode(Artist.self, from: json)
Adding extra logic to the decoding process through a decoding initializer
When the properties of your Swift types don’t match the JSON data your app receives from outside resources, decoding data is not straightforward and requires extra logic.
In those cases, you must provide coding keys matching the fields in the JSON data. Moreover, you also must add a decoding initializer where you perform additional computations.
For example, our app might represent an artist’s name using a single name
property, while the JSON data we need to decode might separate the first, middle, and last names. We combine these three fields in the decoding initializer.
let json = """
{
"first_name": "Rembrandt",
"middle_name": "Harmenszoon",
"last_name": "van Rijn",
}
""".data(using: .utf8)!
struct Artist: Decodable {
let name: String
let profession: String?
enum CodingKeys: String, CodingKey {
case firstName
case middleName
case lastName
case profession
}
init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let firstName = try container.decode(String.self, forKey: .firstName)
let middleName = try container.decode(String.self, forKey: .middleName)
let lastName = try container.decode(String.self, forKey: .lastName)
self.name = firstName + " " + middleName + " " + lastName
self.profession = try container.decodeIfPresent(String.self, forKey: .profession)
}
}
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let artist = try decoder.decode(Artist.self, from: json)
Remember that when you provide a decoding initializer, you must explicitly decode all properties. Swift initializers require you to assign a value to all stored properties, so it is not possible to decode only some properties while relying on automatic decoding for others.
Beware that the decode(_:forKey:)
method of KeyedDecodingContainer
throws an error when a field is missing from the JSON data. In that case, make the corresponding property optional and decode its value using the decodeIfPresent(_:forKey:)
method instead.
Flattening nested JSON object into a single Swift model type
One last use for coding keys is to flatten nested JSON data. Often, a remote API has a more complex representation of its data than our app’s logic requires.
While it’s possible to decode nested JSON data by creating corresponding nested Swift types, these extra types are often unneeded in your apps.
For example, the JSON data we want to decode might contain an artist’s birthplace and spouse name inside nested JSON objects.
let json = """
{
"name": "Rembrandt Harmenszoon van Rijn",
"born": {
"location": "Leiden, Dutch Republic",
"date": "1606-07-15"
},
"profession": "Painter",
"spouse": {
"name": "Saskia van Uylenburgh",
"born": {
"location": "Leeuwarden, Friesland",
"date": "1612-08-02"
}
}
}
""".data(using: .utf8)!
However, our app might not need those extra nested types, so it’s more practical not to nest that information inside another type.
To decode a nested JSON object, we can create coding keys for its properties and use them to get a nested container using the nestedContainer(keyedBy:forKey:)
method of KeyedDecodingContainer
. Then, we use that container to decode the properties we want to flatten into our type.
struct Artist: Decodable {
let name: String
let birthplace: String
let profession: String
let spouse: String
enum CodingKeys: CodingKey {
case name
case born
case profession
case spouse
}
enum BornKeys: CodingKey {
case location
}
enum SpouseKeys: CodingKey {
case name
}
init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.name = try container.decode(String.self, forKey: .name)
self.profession = try container.decode(String.self, forKey: .profession)
let bornContainer = try container.nestedContainer(keyedBy: BornKeys.self, forKey: .born)
self.birthplace = try bornContainer.decode(String.self, forKey: .location)
let spouseContaineer = try container.nestedContainer(keyedBy: SpouseKeys.self, forKey: .spouse)
self.spouse = try spouseContaineer.decode(String.self, forKey: .name)
}
}
let artist = try JSONDecoder().decode(Artist.self, from: json)
Coding keys can also be used to decode JSON data with dynamic keys. However, in that case, Swift enums do not work. You have to construct a dynamic coding key structure instead.
If you want a quick reference of how and when to use coding keys, together with other JSON parsing techniques, download my free cheat sheet below.
FREE GUIDE: Parsing JSON in Swift - The Ultimate Cheat Sheet
Learn the fundamentals of JSON parsing in Swift, advanced decoding techniques, and tasks for app development
DOWNLOAD THE FREE GUIDEMatteo has been developing apps for iOS since 2008. He has been teaching iOS development best practices to hundreds of students since 2015 and he is the developer of Vulcan, a macOS app to generate SwiftUI code. Before that he was a freelance iOS developer for small and big clients, including TomTom, Squla, Siilo, and Layar. Matteo got a master’s degree in computer science and computational logic at the University of Turin. In his spare time he dances and teaches tango.