How does Codable synthesis work? When do you write CodingKeys or a custom init(from:)?
Predict first
What happens when you decode {"name": "Asha"}?
import Foundation
struct User: Decodable {
let name: String
let age: Int
}
let json = Data(#"{"name": "Asha"}"#.utf8)
let u = try? JSONDecoder().decode(User.self, from: json)
print(u as Any)
- age becomes 0
- Decoding throws, u is nil
- Crash
Mental model. The compiler reads your struct and writes the translation both ways. CodingKeys is the glossary that renames terms.
If every stored property is Codable, the compiler synthesizes Encodable and Decodable using property names as keys. A missing key is allowed for an optional property (it decodes as nil); a missing key for a non-optional property throws keyNotFound.
Write CodingKeys to rename, skip, or map a subset of properties. Write a custom init(from:) when you need migrations, nested flattening, polymorphic decoding, or defaults for missing keys.
Synthesis ignores extra JSON keys, and it does not validate values — a string where you expected an int throws a typeMismatch.
Synthesized decoding requires every non-optional key. Make age optional, give it a default in a custom init(from:), or decode it with decodeIfPresent.
Common traps
- “Codable handles schema evolution.” It does not. Adding a non-optional property breaks decoding of old payloads; use an optional or a custom initializer with a default.
- “CodingKeys is only for renaming.” Omitting a case removes that property from the encoding — useful, but easy to do accidentally.
- Relying on key order or exact JSON shape.
JSONDecoderdoes not care about order, but it does care about types and missing keys.
They might follow up with
- What is the difference between
decodeanddecodeIfPresent? The first throws when the key is missing; the second returns nil. - How do you decode a heterogeneous array? Try each type in turn, or use a custom container with a discriminator key.