JSON to Swift: Generate Codable Structs Fast
Codable is Swift's built-in protocol for converting between JSON and your own types, and it works well for the simple case. The trouble starts when your API returns snake_case keys, optional fields, dates in three different formats, and one nested object that occasionally arrives as null. Here's how to write Swift structs that survive real API responses instead of crashing on the first request.
JSON to Swift Type Mapping
| JSON Type | Swift Type | Notes |
|---|---|---|
"string" | String | UTF-8 string, owned |
123 | Int | Default integer type, 64-bit on Apple platforms |
1.5 | Double | All decimal numbers |
true | Bool | |
null | Optional (T?) | Required for any field that can be null or absent |
[1, 2, 3] | [Int] | Typed array |
{} | Custom struct | Nested Codable type |
"2026-01-15" | Date | Needs an explicit date decoding strategy |
Int is 64-bit on every modern Apple platform, so you don't need to pick Int32 vs Int64 unless you're mapping to a specific protocol or file format. Double covers all JSON numbers with decimals. There's no separate Float JSON type, so reaching for Float saves four bytes and costs you precision you'll regret the first time you parse a financial API.
Basic Codable Struct
Given this response:
The Swift struct:
Codable is a typealias for Encodable & Decodable. If you only parse responses, conform to Decodable and Swift skips generating the encode methods. For most app code that only reads from an API, Decodable is enough.
Decoding is one call:
Note jsonData is Data, not String. If you have a string, convert it with .data(using: .utf8)!.
Mapping snake_case Keys
Most JSON APIs use snake_case. Swift uses camelCase. Two ways to handle it.
Option 1, set the strategy on the decoder:
Now user_id maps to userId automatically. This is the right choice when your whole API uses snake_case. Set it once per decoder and forget about it.
Option 2, use a CodingKeys enum on the struct:
CodingKeys is the right choice when only some keys need renaming, when JSON keys collide with reserved Swift words like class or default, or when you want to skip a field by leaving it out of the enum.
Optional Fields
Any JSON field that can be null or missing must be optional in Swift. Required keys with null values crash decoding with valueNotFound. Missing keys crash with keyNotFound. Both errors disappear once you mark the property optional:
If you want a default value instead of nil, you need a custom init(from:) because Swift doesn't synthesize defaults for Decodable. The cleanest workaround uses decodeIfPresent and ??:
decodeIfPresent returns nil for both null values and absent keys, which is what you actually want most of the time.
Nested Structs and Arrays
Nested objects are just nested types:
Arrays of objects work the same way: let posts: [Post]. No extra ceremony, if the inner type is Decodable, the array is too.
Dates Are the Trap
Date is where most Codable bugs live. JSON has no date type, so APIs ship dates as ISO strings, Unix seconds, or milliseconds. Pick the matching strategy on your decoder:
Configuring a DateFormatter without forcing the timezone and locale bites people who release internationally. The defaults use the device's settings, so a date that decodes in San Francisco fails in Tokyo. Always pin both explicitly, and use en_US_POSIX for fixed-format strings to avoid surprises in regions with non-Gregorian calendars.
Generating the Struct
For an unfamiliar API response, paste the JSON into the JSON to Swift converter to get a starter Codable struct. It handles type inference, snake_case mapping, optionals for null fields, and nested structs in a single pass. From there you usually only need to fix dates and tighten a couple of Int vs Int64 choices.
If decoding crashes with a confusing error, paste the raw payload into the JSON validator first to catch trailing commas or unescaped characters before blaming Swift. The JSON formatter is useful for eyeballing deeply nested responses by hand.
One Last Tip
When decode throws, catch the specific DecodingError case instead of the generic Error:
The codingPath tells you exactly which field broke, including its position inside nested objects and arrays. That's the difference between a five-second fix and an hour spent diffing JSON in the debugger.