JSON to Swift
The result appears here.
Generate
Your document never leaves the browser.
Swift Codable structs from a JSON sample — in your browser, never uploaded.
Paste the JSON your app receives and get structs that JSONDecoder fills in. The output targets Swift 5.9 and uses only the standard library and Foundation, so there is no package to add.
Each object becomes a struct conforming to Codable, with let properties in camelCase. When a key had to be renamed to fit Swift, the struct gets a CodingKeys enum mapping each property back to its JSON name, so order_id and placed_at still decode even though the properties are orderId and placedAt.
A key that is absent from some records, or null in some, becomes an optional. note is String? and gift-wrap is Bool?, spelled giftWrap in Swift. As the header comment says, a missing key and a null both decode as nil, and a nil value is left out when the struct is encoded again.
The sample, an order with two line items, pasted as the source
{
"order_id": 1042,
"placed_at": "2026-09-25T10:15:00Z",
"paid": true,
"customer": { "name": "Ada Lovelace", "email": "[email protected]" },
"items": [
{ "sku": "PEN-01", "qty": 2, "price": 3.5, "note": null },
{ "sku": "INK-07", "qty": 1, "price": 12, "note": "Fragile", "gift-wrap": true }
]
}The structs written for it
// Generated by myjsoneditor.com for Codable (Swift 5.9+).
// A missing key and a null both decode as nil, and nil encodes as a missing key.
struct Customer: Codable {
let name: String
let email: String
}
struct Item: Codable {
let sku: String
let qty: Int
let price: Double
let note: String?
let giftWrap: Bool?
enum CodingKeys: String, CodingKey {
case sku
case qty
case price
case note
case giftWrap = "gift-wrap"
}
}
struct Root: Codable {
let orderId: Int
let placedAt: String
let paid: Bool
let customer: Customer
let items: [Item]
enum CodingKeys: String, CodingKey {
case orderId = "order_id"
case placedAt = "placed_at"
case paid
case customer
case items
}
}Hashable, Sendable is off by default. Turn it on and every struct also conforms to Hashable, so it can go into a Set or serve as a dictionary key, and to Sendable, so it can cross an actor boundary under Swift’s strict concurrency checking.
An order id and a flag
{ "order_id": 1042, "paid": true }With Hashable, Sendable on
// Generated by myjsoneditor.com for Codable (Swift 5.9+).
// A missing key and a null both decode as nil, and nil encodes as a missing key.
struct Root: Codable, Hashable, Sendable {
let orderId: Int
let paid: Bool
enum CodingKeys: String, CodingKey {
case orderId = "order_id"
case paid
}
}A field that holds more than one type, or only ever null, is typed JSONValue, and the file includes that enum: a small Codable type with a case for null, a Boolean, a number, a string, an array and an object. Switch over it to read the value, or replace it with a concrete type once you know one.
A struct shape met twice in the document is written once. An integer larger than Int becomes Decimal, which keeps up to 38 significant digits exactly. A key that is a Swift keyword is escaped with backticks, so class is `class`, and a key starting with a digit gets an underscore in front.
A mixed list and a 20-digit number
{ "code": [1, "A"], "account": 12345678901234567890 }JSONValue, included in the file, and Decimal
// Generated by myjsoneditor.com for Codable (Swift 5.9+).
// A missing key and a null both decode as nil, and nil encodes as a missing key.
// Decimal keeps integers past Int exactly up to 38 significant digits; longer ones lose their last digits.
import Foundation
struct Root: Codable {
let code: [JSONValue] // elements: one of: Int, String
let account: Decimal // past Int — Decimal keeps 38 significant digits
}
enum JSONValue: Codable, Hashable, Sendable {
case null
case bool(Bool)
case number(Decimal)
case string(String)
case array([JSONValue])
case object([String: JSONValue])
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if container.decodeNil() { self = .null }
else if let value = try? container.decode(Bool.self) { self = .bool(value) }
else if let value = try? container.decode(Decimal.self) { self = .number(value) }
else if let value = try? container.decode(String.self) { self = .string(value) }
else if let value = try? container.decode([JSONValue].self) { self = .array(value) }
else { self = .object(try container.decode([String: JSONValue].self)) }
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .null: try container.encodeNil()
case .bool(let value): try container.encode(value)
case .number(let value): try container.encode(value)
case .string(let value): try container.encode(value)
case .array(let value): try container.encode(value)
case .object(let value): try container.encode(value)
}
}
}Inference and code generation both run inside this browser tab. The sample is never uploaded, stored on a server or logged, because the page has no server to send it to, and once loaded it carries on working with the connection off.
try JSONDecoder().decode(Root.self, from: data), with your root type in place of Root. The error it throws names the coding path that failed.
Yes, for keys in snake_case; delete the CodingKeys enum and set the decoder’s keyDecodingStrategy. Keys such as gift-wrap still need an explicit mapping.
Double is what most apps expect for a price read from JSON. For money that must add up to the cent, change it to Decimal.