JSON to Swift Codable Struct Generator

Paste a JSON response and get Codable models ready for JSONDecoder in an iOS, macOS or server-side Swift project. The sample is analysed inside your browser and never sent anywhere.

JSON → Swift

Input

Settings

History

Load from URL

Codable models without the boilerplate

Decoding JSON in Swift means declaring a type for every object, getting each property’s type and optionality right, and adding a CodingKeys enum whenever the API’s key names differ from Swift’s. This generator reads a sample payload, merges what every object at the same position looks like, and writes the models in one pass. Nested objects get their own types named after their keys, and the items of an array are named in the singular, so "categories": [...] produces [Category].

The type inference is shared with the other generators on this site, so a sample produces the same structure here as in the Kotlin, TypeScript or Rust output.

How JSON maps to Swift

  • Objects become struct types conforming to Codable, with one stored property per key.
  • A key missing from some objects, or null in some of them, becomes an optional such as String?; JSONDecoder fills it with nil either way.
  • Whole numbers become Int, which is 64-bit on every current Apple platform and on Linux. Other numbers become Double.
  • An integer outside the 64-bit range, or a decimal with more digits than a Double keeps (for example a price written to 20 places), becomes Decimal instead, which JSONDecoder reads digit by digit. Beyond 38 significant digits even Decimal rounds, and a warning says so.
  • Arrays become [Element] and objects used as dictionaries become [String: Value].
  • A position holding different JSON types across the sample, such as 1 in one object and "one" in another, becomes an enum with one case per type and a hand-written init(from:) that tries the most specific type first, so true is never read as a number.
  • A position that is only ever null, or an array that is always empty, gives no type information. It uses a small JSONValue enum that the output includes, which decodes any JSON value.
  • A type that contains itself without an array in between, like a linked-list next field, is emitted as a final class, because a struct cannot contain itself.

Options

Root type name names the top-level type; it is converted to PascalCase, and a JSON array or scalar at the root becomes a typealias with that name.

Declare as chooses struct (value semantics, the usual choice for decoded data) or final class (reference semantics, useful when models are shared and mutated across views).

var properties switches the stored properties from let to var so decoded values can be edited in place.

camelCase property names (on by default) turns user_id into userId and first-name into firstName. Whenever a property name differs from its key, a CodingKeys enum lists every property with its original key, so the mapping is explicit and you do not need keyDecodingStrategy. With the option off, keys that are already valid Swift identifiers are kept as they are and only invalid characters are replaced. Swift keywords such as default are escaped with backticks.

ISO 8601 date-time strings as Date types a string as Date when every value at that position is a full timestamp with a time zone, such as 2024-03-11T10:00:00Z. The comment at the top shows the decoder setup: .iso8601, or a provided .iso8601WithFractionalSeconds strategy when some timestamps have fractions, which the built-in strategy rejects. Date-only strings like 2024-03-11 stay String.

Using the output

The comment at the top shows the decoding call. Add Equatable or Hashable to the conformance list if you compare models or use them in SwiftUI lists, and tighten any type the sample could not pin down. Unknown keys in later responses are ignored by JSONDecoder, so extra fields never break decoding, but a key the models require that is missing does throw: make it optional if the API may omit it. Run the result through the Swift formatter after editing.

Examples

snake_case API response

CodingKeys map user_id to userId; verified is optional because one follower lacks it, and the always-null avatar_url uses JSONValue?.

Input
{
  "user_id": 42,
  "display_name": "Aisha Tan",
  "avatar_url": null,
  "followers": [
    { "user_id": 7, "display_name": "Ben" },
    { "user_id": 9, "display_name": "Chloé", "verified": true }
  ]
}
Output
// Decode with JSONDecoder:
//   let value = try JSONDecoder().decode(Profile.self, from: data)

import Foundation

struct Profile: Codable {
    let userId: Int
    let displayName: String
    let avatarUrl: JSONValue?
    let followers: [Follower]

    enum CodingKeys: String, CodingKey {
        case userId = "user_id"
        case displayName = "display_name"
        case avatarUrl = "avatar_url"
        case followers
    }
}

struct Follower: Codable {
    let userId: Int
    let displayName: String
    let verified: Bool?

    enum CodingKeys: String, CodingKey {
        case userId = "user_id"
        case displayName = "display_name"
        case verified
    }
}

/// Any JSON value, for positions where the sample shows no type (only null, or empty arrays).
enum JSONValue: Codable, Hashable {
    case null
    case bool(Bool)
    case number(Double)
    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(Double.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)
        }
    }
}
Open this example in the tool

Timestamps as Date, mutable class

Both timestamps become Date and the fractional seconds add a flexible decoding strategy; the date-only field stays a String.

Input
{
  "id": "evt_1",
  "startsAt": "2024-03-11T09:30:00Z",
  "updatedAt": "2024-03-11T10:00:00.250+08:00",
  "day": "2024-03-11"
}
Output
// Decode with JSONDecoder:
//   let decoder = JSONDecoder()
//   decoder.dateDecodingStrategy = .iso8601WithFractionalSeconds
//   let value = try decoder.decode(Event.self, from: data)
// Encode dates back as ISO 8601 with encoder.dateEncodingStrategy = .iso8601.

import Foundation

final class Event: Codable {
    var id: String
    var startsAt: Date
    var updatedAt: Date
    var day: String
}

extension JSONDecoder.DateDecodingStrategy {
    /// ISO 8601 date-times with or without fractional seconds (.iso8601 rejects fractions).
    static var iso8601WithFractionalSeconds: Self {
        .custom { decoder in
            let container = try decoder.singleValueContainer()
            let text = try container.decode(String.self)
            let formatter = ISO8601DateFormatter()
            formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
            if let date = formatter.date(from: text) {
                return date
            }
            formatter.formatOptions = [.withInternetDateTime]
            if let date = formatter.date(from: text) {
                return date
            }
            throw DecodingError.dataCorruptedError(in: container, debugDescription: "Expected an ISO 8601 date-time, got \(text)")
        }
    }
}
Open this example in the tool

Large numbers and mixed values

The ID beyond Int64 and the over-precise amount become Decimal; the mixed array becomes [Value?], an enum with double and string cases.

Input
{
  "snowflake": 12345678901234567890,
  "amount": 19.999999999999999999,
  "count": 3,
  "values": [1, "two", 3.5, null]
}
Output
// Decode with JSONDecoder:
//   let value = try JSONDecoder().decode(Root.self, from: data)

import Foundation

struct Root: Codable {
    let snowflake: Decimal
    let amount: Decimal
    let count: Int
    let values: [Value?]
}

enum Value: Codable {
    case double(Double)
    case string(String)

    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        if let value = try? container.decode(Double.self) {
            self = .double(value)
            return
        }
        if let value = try? container.decode(String.self) {
            self = .string(value)
            return
        }
        throw DecodingError.typeMismatch(Value.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Expected Double or String"))
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        switch self {
        case .double(let value):
            try container.encode(value)
        case .string(let value):
            try container.encode(value)
        }
    }
}
Open this example in the tool

Common errors and how to fix them

ErrorCauseFix
Trailing comma before '}'
Explained
The sample is not valid JSON, often because of a trailing comma copied from JavaScript.Delete the comma after the last member; the JSON formatter can also fix it for you.
Some numbers have more than 38 significant digits; Swift's Decimal keeps 38, so those values lose precision.A warning: a number in the sample is longer than Decimal can represent.Ask the API to send such values as strings and type the property as String.
keyNotFound(CodingKeys(stringValue: "…")) at runtimeA key that was present in every sample object is missing from a real response, so its property was generated as non-optional.Add more varied objects to the sample, or make that property optional by hand.
typeMismatch(Swift.Int, …) at runtimeThe sample only had whole numbers at a position where the API sometimes sends decimals.Change the property to Double or Decimal, or include a decimal value in the sample.

Frequently asked questions

Do I need a third-party library?

No. The models use only Codable and Foundation, so they work with JSONDecoder on iOS, macOS, watchOS, tvOS and Linux.

Why CodingKeys instead of convertFromSnakeCase?

Explicit keys work for any naming style, including kebab-case and keys with spaces, and they keep each model correct regardless of how the decoder is configured.

How are Int and Double chosen?

By how the numbers are written: a position with only integer literals becomes Int, and any decimal or exponent at that position makes it Double.

Is my JSON uploaded?

No. Inference and code generation both run locally in this browser tab.

Can it generate Equatable or Hashable conformances?

Add them to the generated declarations yourself: Swift synthesises both automatically when every property type supports them, which all generated types do.

Related tools