How to Convert JSON to Swift Structs (Codable, CodingKeys, Optionals)
Decoding JSON in Swift means writing a Codable struct for the shape you expect, and the fiddly parts are the CodingKeys that map camelCase properties to snake_case keys and the optionals for fields that may be missing. Generating the structs from a real sample gets both right. This guide covers how the mapping works, the fastest way to do it, and the Swift-specific gotchas around Codable, CodingKeys, and optionals.
The fastest route: a browser generator
For a one-off, paste the JSON into the JSON to Swift converter. It reads the sample, infers the type of every property, and writes structs that conform to Codable: nested objects become their own structs, arrays become typed Swift arrays, numbers resolve to Int or Double, and a CodingKeys enum appears only when a property name differs from its key. Because it runs entirely in your browser, the JSON never leaves your machine, which matters when the sample is a real production response.
If the JSON will not convert, it is almost always invalid rather than genuinely unconvertible. Run it through the JSON parser to find the exact syntax error, or format it so you can see the structure first.
How JSON maps to Swift
A generator makes the same decisions every time. Knowing them tells you what to expect.
- Objects become
Codablestructs. Each object turns into astructconforming toCodable, so one call decodes it. - Nested objects get their own struct. Rather than inlining a deep object, the generator names it and references it from the parent.
- Arrays become Swift arrays.
["a","b"]becomes[String]; an array of objects becomes[Item]with a generatedItemstruct. - Numbers split into
IntandDouble. Whole numbers map toInt; anything with a decimal point maps toDouble.
Here is the shape of a simple conversion:
{ "id": 1, "name": "Ada", "tags": ["admin", "dev"] }
becomes:
struct Root: Codable {
let id: Int
let name: String
let tags: [String]
}
No CodingKeys enum appears here because every key is already a valid, matching Swift identifier.
CodingKeys: only when they are needed
Codable matches JSON keys to properties by name. When a key is snake_case but you want a camelCase property, the two will not connect unless you provide a CodingKeys enum (or set the decoder’s keyDecodingStrategy). The generator emits the enum only when at least one property differs:
struct Root: Codable {
let orderId: String
enum CodingKeys: String, CodingKey {
case orderId = "order_id"
}
}
If every key already matches a valid identifier, no enum is emitted, which keeps the output clean. A whole-app alternative is to set decoder.keyDecodingStrategy = .convertFromSnakeCase, which lets you drop the enum entirely when the only difference is snake_case, though an explicit CodingKeys is clearer when keys are irregular.
Optionals: missing and null
A property becomes optional (T?) when its key is missing from some objects in an array, or when its value is sometimes null. That matters because Codable throws if a non-optional property’s key is absent during decoding. Marking it optional lets it decode to nil instead:
let qty: Int?
This is why you should paste a representative sample with several records: a single object makes every field look required, so the generator cannot tell which ones are actually optional.
When you see AnyCodable
A field that is only ever null, an empty array, or holds more than one unrelated type has no single Swift type. Swift’s Codable has no built-in “any” type, so the generator marks it AnyCodable, which is a small, widely used Swift package. Add the package, or replace the field with a concrete type once you paste a fuller sample. To confirm the shape of a deeply nested payload first, expand it in the JSON viewer.
Decoding in Swift
Once you have the structs, decoding is one call:
let data = Data(jsonString.utf8)
let order = try JSONDecoder().decode(Root.self, from: data)
Because the structs are Codable, encoding back to JSON works the same way with JSONEncoder. Two decoder options are worth knowing: keyDecodingStrategy = .convertFromSnakeCase handles snake_case keys without per-property CodingKeys, and dateDecodingStrategy maps date strings to Date when your model uses it.
The gotchas that bite
- Non-optional missing keys throw. If a key is sometimes absent and the property is not optional,
decodethrows. Feed an array of several records so the generator marks the right properties optional. - snake_case without CodingKeys. Without the enum or
.convertFromSnakeCase, snake_case keys silently fail to bind. Generation emits the enum whenever a name differs. - Int vs Double. A field that is an integer in one record and a decimal in another should be
Double; a representative sample makes that explicit, otherwiseIntcan fail to decode a19.99. - AnyCodable needs a package. Mixed or null-only fields use
AnyCodable, which is not in the standard library. Add the package or tighten the type. - Let vs var. Decoded models are usually
let; switch tovaronly if you need to mutate them after decoding.
Frequently asked questions
How do I convert JSON to a Swift struct?
Paste the JSON into the JSON to Swift converter and it writes Codable structs, inferring nested structs, arrays, Int/Double, optionals, and CodingKeys where keys differ. Then call try JSONDecoder().decode(Root.self, from: data).
When do I need a CodingKeys enum?
Only when a property name differs from the JSON key, such as a camelCase property for a snake_case key. If the names already match, you do not need one, and the generator omits it. Alternatively set keyDecodingStrategy = .convertFromSnakeCase.
Why is my Swift decode throwing “keyNotFound”?
A non-optional property’s key was absent in the JSON. Make that property optional (T?) so it decodes to nil, which is what the generator does for keys missing in some records.
How are optionals decided?
A property is optional when its key is missing from some objects in the sample, or when its value is sometimes null. Paste several records so the generator can see which keys are not always present.
For the format underneath all this, see the guide on what JSON is, the same idea for the backend in converting JSON to C#, and the roundup of free JSON tools for the rest of the toolkit.
Leave a Reply