Reading — step 1 of 6
Learn
Codable (Swift 4+) is the standard way to encode/decode types to/from data formats — primarily JSON. It replaces hand-written serialization with an automatic, type-safe approach.
The basics
import Foundation
struct User: Codable {
let id: Int
let name: String
let email: String
}
// Decode JSON to User
let json = #"{"id":42,"name":"Alice","email":"[email protected]"}"#
let data = json.data(using: .utf8)!
let user = try JSONDecoder().decode(User.self, from: data)
print(user.name) // Alice
// Encode User to JSON
let encoded = try JSONEncoder().encode(user)
let jsonString = String(data: encoded, encoding: .utf8)!
print(jsonString)
Codable is Encodable & Decodable. If all your stored properties are Codable (which is true for common types), you get conformance for free.
CodingKeys for renaming
Most APIs use snake_case JSON; Swift uses camelCase. Map them with CodingKeys:
struct User: Codable {
let id: Int
let firstName: String
let lastName: String
enum CodingKeys: String, CodingKey {
case id
case firstName = "first_name"
case lastName = "last_name"
}
}
Or use the encoder's keyEncodingStrategy for batch conversion:
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
Date and Data formatting
Dates and binary data need explicit format choices:
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
// or .millisecondsSince1970, .secondsSince1970, .formatted(formatter), .custom(...)
let encoder = JSONEncoder()
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
Optional fields
Missing JSON fields decode to nil for Optional properties:
struct User: Codable {
let name: String
let email: String? // nil if missing in JSON
}
Nested types
Nested Codables work transparently:
struct Order: Codable {
let id: Int
let user: User
let items: [Item]
}
struct Item: Codable {
let name: String
let price: Double
}
The whole tree decodes/encodes recursively.
Custom decoding for tricky JSON
For unusual shapes, implement init(from:):
struct Distance: Codable {
let meters: Double
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
// API returns the number as a string for some reason:
let str = try container.decode(String.self)
guard let value = Double(str) else {
throw DecodingError.dataCorruptedError(in: container, debugDescription: "not a number")
}
self.meters = value
}
}
Handling errors
do {
let user = try JSONDecoder().decode(User.self, from: data)
} catch DecodingError.keyNotFound(let key, _) {
print("missing key: \(key.stringValue)")
} catch DecodingError.typeMismatch(let type, let context) {
print("type mismatch: expected \(type), \(context.debugDescription)")
} catch {
print("other: \(error)")
}
DecodingError cases tell you exactly what went wrong — useful for surfacing API bugs.
Common mistakes
- Forgetting
Codableon nested types — error "Type does not conform." Mark the entire tree. - Mismatched JSON key cases — use CodingKeys or
keyDecodingStrategy. - Decoding
Datewithout specifying strategy — defaults are sometimes wrong. Be explicit. - Holding the decoded result by reference when it's a struct — structs are copied. Mutate or pass around as needed.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…