JSON to Swift Converter — Codable Structs from JSON
iOS and macOS developers reach for this page when a new endpoint lands and the model layer is still empty. Paste the response body from the network tab, name the root struct, and the converter prints Swift structs with Codable conformance ready to drop into the project. Naming, date typing, and null policy are all controlled below the panes. Hand-writing CodingKeys for a deep payload costs an afternoon and drifts from the API the day after it ships.
How It Works
The conversion starts with JSON.parse, so malformed input stops at the red banner with the parser message and no code is produced. A typed walk then visits every value and records its kind: strings, booleans, nulls, whole numbers as Int, decimals as Double, arrays, and nested objects. Objects become struct bodies pushed onto a registry in post-order, which means each child finishes before its parent and the output prints children first. A root that is not an object ends as a typealias line instead of a struct.
Property names follow three rules: keys convert to camelCase when the box is checked, a key starting with a digit gets a leading `_`, and Swift keywords such as class and default are wrapped in backticks so the code compiles. CodingKeys appear only when at least one property name differs from its JSON key. Arrays sample their elements to decide a type: a uniform list becomes [String] or [Int], an Int and Double mixture widens to [Double], and anything else falls back to [Any] with an issue on its path. Null becomes Any? with a warning because a null carries no type information. With Codable on, the keyword lands in every struct header, and date mode prepends a commented decoder strategy block once any field became a Date.
Before and After
{"first_name":"Ada","age":36} struct Root: Codable {
let firstName: String
let age: Int
enum CodingKeys: String, CodingKey {
case firstName = "first_name"
case age
}
} {"user":{"id":1},"items":[{"sku":"a"}]} struct User: Codable {
let id: Int
}
struct Items: Codable {
let sku: String
}
struct Root: Codable {
let user: User
let items: [Items]
} Use Cases
| Developer | Production | Workflow |
|---|---|---|
| Scaffolding models for a newly captured endpoint response | Decoding live API payloads at the network layer | Batch-generating structs from a folder of saved fixtures |
| Replacing hand-typed decoding code that lags field renames | Feeding mock JSON into SwiftUI previews from committed samples | Reviewing payload drift by diffing old and new models |
| Auditing a model layer for date fields still typed as String | Decoding queue messages with the same structs as the API | Migrating an endpoint to snake_case keys with generated CodingKeys |
Common Mistakes and Troubleshooting
- Expecting a null to reveal its type. Nulls become Any? with an issue on the path; replace the null with a real sample value and regenerate instead of guessing in Swift code.
- Mixed arrays losing type info. An array holding strings and objects becomes [Any]; sample a payload where the element kind is uniform, then handle the odd items by hand.
- Date strings staying String. Keys ending in _at, date, time, or timestamp only become Date when the ISO 8601 box is checked, and the decoder comment prints with the structs.
- Treating a root-array typealias as Codable. A top-level array emits typealias Root = [Int], which decodes lists fine but carries no CodingKeys.
- 1.0 values coming out as Int. JSON.parse turns 1.0 into a whole number, so a field mixing 1 and 1.5 flips; widen it to Double by hand when the API is inconsistent.
- Keyword collisions on keys named class or default. Those property names are wrapped in backticks automatically, so the output compiles without edits.
Best Practices
- Paste a full API response instead of a trimmed snippet; fields missing from the sample are missing from the structs and surface as decode failures after release.
- Name the root struct after the endpoint, such as InvoicePayload or SearchResponse, rather than leaving Root in a file three modules deep.
- Keep camelCase on for snake_case APIs; generated CodingKeys hold the original spellings so a server-side rename fails loudly with a clear key path.
- Prefer decoder strategies over per-field parsing: one .iso8601 strategy covers every Date property and keeps struct bodies short.
- Drop the output into an Xcode playground with the same sample and decode it; a green result means the CodingKeys line up.
- Regenerate whenever the API version changes and diff the files; added and removed properties are exactly what a reviewer should see.
Frequently Asked Questions
How does Codable synthesis remove manual decoding code?
Declaring Codable gives the compiler a generated init(from:) and encode(to:), so one JSONDecoder().decode call returns a typed model. Without it you request every key by hand and cast every value. Regenerating from a fresh sample deletes that boilerplate per endpoint.
When does the converter emit a CodingKeys enum?
Only when a property name differs from its JSON key. With camelCase conversion on, first_name becomes the property firstName and the output adds case firstName = "first_name" inside enum CodingKeys: String, CodingKey. When every key already matches its property, no enum is printed.
What happens when a JSON value is null?
A null reveals no type, so the field becomes Any? and an issue appears under the widget with its path. For a sample of nickname set to null beside score 12 you get let nickname: Any? next to let score: Int. Swap the null for a real value of the expected kind and regenerate for a strong type.
How are nested objects and arrays turned into structs?
Each nested object becomes a struct named after the property holding it, printed above the parent so the file compiles top to bottom. Arrays of objects follow the same rule: items: [Items] pairs with a struct Items declared once. Primitive arrays stay inline as [String] or [Int].
How does Date handling work?
With the ISO 8601 box checked, string keys ending in _at, date, time, or timestamp become Date, and a commented decoder block showing .iso8601 prints above the structs. That strategy line matters because the decoder default throws on ISO strings. Clear the box when your timestamps stay as plain strings or use other key names.
What do mixed arrays and empty arrays produce?
A uniform array becomes [String], [Int], or [Double], and an Int with Double mix is widened to [Double]. Any other mixture falls back to [Any] with an issue listing the types that were seen. Empty arrays do the same because they have nothing to sample.
Why does a top-level JSON array become a typealias?
Codable structs decode objects, so a root array emits typealias Root = [Int] instead of a struct body. The alias still decodes a list payload, but it carries no CodingKeys and no room for methods. Wrap the list in your own object when you need a container type.