Swift's Codable protocol makes converting models to and from JSON straightforward. The compiler can synthesize most implementations, but APIs do not always use the same names and structure as your app. In those cases, a custom implementation gives you full control.
Renaming JSON Keys
Use a CodingKeys enum when a JSON key differs from the Swift property name:
struct User: Codable {
let id: Int
let displayName: String
let isActive: Bool
enum CodingKeys: String, CodingKey {
case id
case displayName = "display_name"
case isActive = "is_active"
}
}
The enum only needs to contain properties that should be encoded and decoded.
Decoding Nested Values
Sometimes the value you need is nested inside another object:
struct Article: Decodable {
let title: String
let authorName: String
enum CodingKeys: String, CodingKey {
case title
case author
}
enum AuthorKeys: String, CodingKey {
case name
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
title = try container.decode(String.self, forKey: .title)
let author = try container.nestedContainer(
keyedBy: AuthorKeys.self,
forKey: .author
)
authorName = try author.decode(String.self, forKey: .name)
}
}
This keeps the app's model simple even when the API response is deeply nested.
Providing Default Values
Use decodeIfPresent when a key may be missing:
struct Settings: Decodable {
let notificationsEnabled: Bool
enum CodingKeys: String, CodingKey {
case notificationsEnabled = "notifications_enabled"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
notificationsEnabled = try container.decodeIfPresent(
Bool.self,
forKey: .notificationsEnabled
) ?? true
}
}
Encoding a Custom Structure
Custom encoding works in the opposite direction:
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(displayName, forKey: .displayName)
try container.encode(isActive, forKey: .isActive)
}
Wrap up
Start with the synthesized Codable implementation and customize it only when the external data format requires it. CodingKeys, nested containers, and decodeIfPresent cover most real-world API responses.
Resources: