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.

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

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

Your email address will not be published. Required fields are marked *