TL;DR
JSON 转 Swift 工具通过 Codable 协议从任意 JSON 数据生成类型安全的 Swift 结构体,利用 CodingKeys 自动处理 snake_case 到 camelCase 的映射,消除手动编码。 立即使用免费的 JSON 转 Swift 工具 →
核心要点
- Conform your Swift struct to
Codableand get automatic JSON encode/decode with zero boilerplate. - Use
CodingKeysto map snake_case JSON keys to camelCase Swift properties, or setkeyDecodingStrategy = .convertFromSnakeCaseglobally. - Declare properties as
Optional(e.g.,String?) to gracefully handle missing or null JSON fields. - Set
decoder.dateDecodingStrategy = .iso8601to decode ISO 8601 date strings directly into SwiftDatevalues. - Enum types conforming to
String, Codableautomatically map JSON string values to strongly typed enum cases. - Use
async/awaitwithURLSession.data(from:)andJSONDecoderfor clean, concise networking code. - Implement a custom
init(from:)for complex transformations that automatic synthesis cannot handle.
为什么使用 Codable 将 JSON 转换为 Swift 结构体?
Before Swift 4, JSON parsing required manually casting values from JSONSerialization output — verbose, error-prone, and completely untyped. The Codable protocol changed everything by letting the Swift compiler synthesize all encoding and decoding logic automatically from your type declarations.
Codable vs Manual Parsing vs JSONSerialization
| Approach | Type Safety | Boilerplate | Best For |
|---|---|---|---|
| Codable + JSONDecoder | Full | Minimal | All modern Swift projects |
| JSONSerialization | None | High | Dynamic / unknown schemas |
| SwiftyJSON / Argo | Partial | Medium | Legacy codebases |
With Codable, you describe what your data looks like through Swift types — the compiler figures out how to parse it. This gives you compile-time error detection, full IDE autocomplete, and easy refactoring support.
Generate Swift structs from JSON instantly with our free tool →
Basic Swift Struct with Codable and JSONDecoder
The simplest case: your JSON keys already match Swift property names exactly. Given this JSON:
{
"id": 1,
"name": "Alice",
"email": "alice@example.com",
"isActive": true,
"score": 98.5
}The corresponding Swift struct and decoding code:
import Foundation
// 1. Declare your struct conforming to Codable
struct User: Codable {
let id: Int
let name: String
let email: String
let isActive: Bool
let score: Double
}
// 2. Decode from JSON Data
let jsonString = """
{
"id": 1,
"name": "Alice",
"email": "alice@example.com",
"isActive": true,
"score": 98.5
}
"""
let data = Data(jsonString.utf8)
let decoder = JSONDecoder()
let user = try decoder.decode(User.self, from: data)
print(user.name) // "Alice"
print(user.score) // 98.5
// 3. Encode back to JSON
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let encoded = try encoder.encode(user)
print(String(data: encoded, encoding: .utf8)!)CodingKeys: Mapping snake_case JSON to camelCase Swift
REST APIs commonly return snake_case field names (first_name, user_id), while Swift convention prefers camelCase. Use a nested CodingKeys enum to bridge the gap:
// JSON: { "user_id": 42, "first_name": "Bob", "last_name": "Jones", "email_address": "bob@example.com" }
struct User: Codable {
let userId: Int
let firstName: String
let lastName: String
let emailAddress: String
enum CodingKeys: String, CodingKey {
case userId = "user_id"
case firstName = "first_name"
case lastName = "last_name"
case emailAddress = "email_address"
}
}
// Alternative: use keyDecodingStrategy to avoid per-type CodingKeys
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
// Now "first_name" maps to firstName automatically — no CodingKeys neededkeyDecodingStrategy = .convertFromSnakeCase is the global solution and eliminates repetitive CodingKeys for all your types at once. Use explicit CodingKeys when you need fine-grained control or when names do not follow a consistent pattern.
Optional Properties and Null Handling
APIs often return fields that may be absent or explicitly null. Swift optional types handle both cases seamlessly:
struct UserProfile: Codable {
let id: Int
let username: String
let bio: String? // nil if absent or null in JSON
let avatarUrl: URL? // nil if absent or null
let followersCount: Int // required, throws if missing
}
// JSON with missing optional fields — decodes successfully
let json = """
{
"id": 7,
"username": "swiftdev",
"followersCount": 320
}
"""
let profile = try JSONDecoder().decode(UserProfile.self, from: Data(json.utf8))
print(profile.bio ?? "No bio") // "No bio"
print(profile.avatarUrl?.absoluteString ?? "No avatar")
// Provide default values with custom init(from:)
struct Config: Codable {
let timeout: Int
let retries: Int
init(from decoder: Decoder) throws {
let c = try decoder.container(keyedBy: CodingKeys.self)
timeout = try c.decodeIfPresent(Int.self, forKey: .timeout) ?? 30
retries = try c.decodeIfPresent(Int.self, forKey: .retries) ?? 3
}
}Nested Structs and Arrays
JSON often contains nested objects and arrays. Codable handles multiple levels of nesting recursively — simply declare nested Swift structs and arrays of the appropriate types:
// JSON:
// {
// "order": { "id": "ORD-001", "total": 149.99 },
// "customer": { "name": "Alice", "email": "alice@example.com" },
// "items": [
// { "sku": "SWIFT-BOOK", "qty": 2, "price": 49.99 },
// { "sku": "XCODE-PLUGIN", "qty": 1, "price": 50.01 }
// ]
// }
struct OrderResponse: Codable {
let order: Order
let customer: Customer
let items: [OrderItem]
}
struct Order: Codable {
let id: String
let total: Double
}
struct Customer: Codable {
let name: String
let email: String
}
struct OrderItem: Codable {
let sku: String
let qty: Int
let price: Double
}
let response = try JSONDecoder().decode(OrderResponse.self, from: data)
print(response.order.id) // "ORD-001"
print(response.items.count) // 2
print(response.items[0].sku) // "SWIFT-BOOK"
// Decode a top-level JSON array
let users = try JSONDecoder().decode([User].self, from: arrayData)Enums with RawRepresentable for JSON String Values
When a JSON field has a fixed set of string values, model it as a Swift enum conforming to String, Codable. Swift automatically maps raw values to enum cases:
enum SubscriptionTier: String, Codable {
case free = "free"
case pro = "pro"
case enterprise = "enterprise"
}
enum NotificationStatus: String, Codable {
case sent
case delivered
case failed
// raw values default to case name strings: "sent", "delivered", "failed"
}
struct Account: Codable {
let id: Int
let tier: SubscriptionTier
let lastNotification: NotificationStatus?
}
let json = """{ "id": 5, "tier": "pro", "lastNotification": "delivered" }"""
let account = try JSONDecoder().decode(Account.self, from: Data(json.utf8))
print(account.tier) // SubscriptionTier.pro
// Handle unknown values gracefully with an unknown case
enum Status: String, Codable {
case active, inactive, pending
case unknown
init(from decoder: Decoder) throws {
let raw = try decoder.singleValueContainer().decode(String.self)
self = Status(rawValue: raw) ?? .unknown
}
}Date Handling with DateDecodingStrategy
Configure JSONDecoder.dateDecodingStrategy before decoding to convert JSON date representations into Swift Date values:
struct Event: Codable {
let title: String
let startsAt: Date
let endsAt: Date
let createdAt: Date
}
// ISO 8601 strings: "2024-06-15T09:00:00Z"
let isoDecoder = JSONDecoder()
isoDecoder.keyDecodingStrategy = .convertFromSnakeCase
isoDecoder.dateDecodingStrategy = .iso8601
let event = try isoDecoder.decode(Event.self, from: data)
// Unix timestamps (seconds since 1970)
let tsDecoder = JSONDecoder()
tsDecoder.dateDecodingStrategy = .secondsSince1970
// or: .millisecondsSince1970
// Custom date format: "2024-06-15 09:00:00"
let customDecoder = JSONDecoder()
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
formatter.locale = Locale(identifier: "en_US_POSIX")
customDecoder.dateDecodingStrategy = .formatted(formatter)
// Encoding dates
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .iso8601
let encoded = try encoder.encode(event)Error Handling with DecodingError
JSONDecoder throws typed DecodingError values that tell you exactly which key or type caused the failure. Always handle these errors in production code:
func decodeUser(from data: Data) -> User? {
do {
return try JSONDecoder().decode(User.self, from: data)
} catch DecodingError.keyNotFound(let key, let context) {
print("Missing key '\(key.stringValue)' at: \(context.codingPath)")
} catch DecodingError.typeMismatch(let type, let context) {
print("Type mismatch — expected \(type) at: \(context.codingPath)")
} catch DecodingError.valueNotFound(let type, let context) {
print("Null value for \(type) at: \(context.codingPath)")
} catch DecodingError.dataCorrupted(let context) {
print("Data corrupted: \(context.debugDescription)")
} catch {
print("Unknown error: \(error.localizedDescription)")
}
return nil
}
// Swift 5.7+ result builder style
func safeDecodingUser(data: Data) -> Result<User, Error> {
Result { try JSONDecoder().decode(User.self, from: data) }
}Custom init(from:) for Complex Transformations
When automatic synthesis is not enough — such as when merging multiple JSON fields, transforming values, or reading from a non-keyed container — implement init(from decoder: Decoder) manually:
// JSON: { "lat": 37.7749, "lng": -122.4194, "label": "San Francisco" }
// We want: struct Location with a CLLocationCoordinate2D property
import CoreLocation
struct Location: Decodable {
let coordinate: CLLocationCoordinate2D
let label: String
enum CodingKeys: String, CodingKey {
case lat, lng, label
}
init(from decoder: Decoder) throws {
let c = try decoder.container(keyedBy: CodingKeys.self)
let lat = try c.decode(Double.self, forKey: .lat)
let lng = try c.decode(Double.self, forKey: .lng)
coordinate = CLLocationCoordinate2D(latitude: lat, longitude: lng)
label = try c.decode(String.self, forKey: .label)
}
}
// Handling polymorphic JSON with a "type" discriminator
struct AnyShape: Decodable {
let shape: any ShapeProtocol
enum TypeKey: String, CodingKey { case type }
init(from decoder: Decoder) throws {
let meta = try decoder.container(keyedBy: TypeKey.self)
let kind = try meta.decode(String.self, forKey: .type)
switch kind {
case "circle":
shape = try Circle(from: decoder)
case "rectangle":
shape = try Rectangle(from: decoder)
default:
throw DecodingError.dataCorruptedError(
forKey: .type, in: meta,
debugDescription: "Unknown shape type: \(kind)"
)
}
}
}Networking with URLSession and async/await
Combining URLSession async/await with JSONDecoder produces clean, readable networking code with minimal ceremony:
import Foundation
// Reusable API client
struct APIClient {
private let decoder: JSONDecoder = {
let d = JSONDecoder()
d.keyDecodingStrategy = .convertFromSnakeCase
d.dateDecodingStrategy = .iso8601
return d
}()
func fetch<T: Decodable>(_ type: T.Type, from url: URL) async throws -> T {
let (data, response) = try await URLSession.shared.data(from: url)
guard let http = response as? HTTPURLResponse,
(200...299).contains(http.statusCode) else {
throw URLError(.badServerResponse)
}
return try decoder.decode(T.self, from: data)
}
}
// Usage
struct Post: Decodable {
let id: Int
let title: String
let body: String
let userId: Int // decoded from "user_id" via convertFromSnakeCase
}
let client = APIClient()
let url = URL(string: "https://jsonplaceholder.typicode.com/posts/1")!
let post = try await client.fetch(Post.self, from: url)
print(post.title)
// Fetch an array
let postsUrl = URL(string: "https://jsonplaceholder.typicode.com/posts")!
let posts = try await client.fetch([Post].self, from: postsUrl)
print("Loaded \(posts.count) posts")JSONEncoder: Serializing Swift Structs to JSON
JSONEncoder mirrors the decoder API. Use it to convert Swift structs back to JSON for API requests or local storage:
struct CreateUserRequest: Encodable {
let firstName: String
let lastName: String
let email: String
}
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase // camelCase → snake_case
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
encoder.dateEncodingStrategy = .iso8601
let request = CreateUserRequest(
firstName: "Carol",
lastName: "Chen",
email: "carol@example.com"
)
let jsonData = try encoder.encode(request)
let jsonString = String(data: jsonData, encoding: .utf8)!
// Output:
// {
// "email" : "carol@example.com",
// "first_name" : "Carol",
// "last_name" : "Chen"
// }
// Use in a URLRequest
var urlRequest = URLRequest(url: URL(string: "https://api.example.com/users")!)
urlRequest.httpMethod = "POST"
urlRequest.httpBody = jsonData
urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")Generate Swift Structs from JSON Instantly
Paste your JSON and get ready-to-use Swift structs with Codable conformance, CodingKeys, and proper optionals — no manual typing required.
Try JSON to Swift Tool →常见问题
What is the Codable protocol in Swift and why should I use it for JSON?
Codable is a type alias for the combination of Encodable and Decodable protocols introduced in Swift 4. Conforming a struct or class to Codable lets Swift automatically synthesize all the encoding and decoding logic at compile time — no manual key lookups or type casts required. JSONDecoder and JSONEncoder do the heavy lifting, giving you compile-time type safety, IDE autocomplete, and refactoring support that raw dictionary access cannot provide.
What is CodingKeys and when do I need it?
CodingKeys is a nested enum inside your Codable type that maps Swift property names to their corresponding JSON keys. You only need it when the JSON key names differ from the property names you want to use in Swift. For example, a JSON field "first_name" (snake_case) can be mapped to a Swift property firstName (camelCase) by declaring: case firstName = "first_name". If all names match, CodingKeys can be omitted and Swift synthesizes the mapping automatically. Alternatively, set decoder.keyDecodingStrategy = .convertFromSnakeCase to handle snake_case globally without per-type CodingKeys.
How do I handle optional or missing JSON fields in Swift Codable?
Declare the property as an Optional (e.g., var bio: String?) in your Swift struct. If the JSON key is absent or its value is null, JSONDecoder assigns nil to the optional property instead of throwing an error. If you want a default value instead of nil, implement a custom init(from:) and use decodeIfPresent together with the nil-coalescing operator: self.bio = try container.decodeIfPresent(String.self, forKey: .bio) ?? "No bio".
How do I decode a JSON array into a Swift array?
Pass an array type directly to JSONDecoder.decode: let users = try JSONDecoder().decode([User].self, from: data). Because Swift generics preserve type information at runtime (unlike Java type erasure), no extra wrapper is needed. For a top-level JSON array you decode [User].self; for a nested array field, simply declare the property as [Item] in your struct and Codable handles it automatically.
How do I decode JSON dates into Swift Date values?
Set the dateDecodingStrategy on your JSONDecoder before calling decode. The most common options are: .iso8601 for ISO 8601 strings like "2024-01-15T10:30:00Z"; .secondsSince1970 or .millisecondsSince1970 for numeric Unix timestamps; and .formatted(DateFormatter) for custom patterns. Example: let decoder = JSONDecoder(); decoder.dateDecodingStrategy = .iso8601; let event = try decoder.decode(Event.self, from: data). Declare the Swift property as Date and Codable handles the rest.
How do I map a JSON string value to a Swift enum?
Declare an enum that conforms to String and Codable (or just Decodable). Swift automatically maps JSON string values to enum cases by raw value. Example: enum Status: String, Codable { case active = "active"; case inactive = "inactive"; case pending = "pending" }. In your struct, use var status: Status. If the JSON contains an unexpected string, decoding throws a DecodingError.dataCorrupted. To be resilient, use an optional (var status: Status?) or add an unknown case.
What is the difference between struct and class for Codable in Swift?
Both structs and classes can conform to Codable. Structs are value types (copied on assignment), are thread-safe by default, and are the idiomatic Swift choice for data models. Classes are reference types and are useful when you need inheritance or shared mutable state. For JSON models, prefer structs unless you need class-specific features. The Codable synthesis works identically for both, but structs do not require an explicit initializer — the compiler generates a memberwise init automatically.
How do I implement a custom init(from:) for complex JSON transformations?
Implement init(from decoder: Decoder) throws manually when automatic synthesis is not sufficient. Inside, call decoder.container(keyedBy: CodingKeys.self) to get a keyed container, then use container.decode(_:forKey:) for required fields and container.decodeIfPresent(_:forKey:) for optional fields. You can also read a nested container with container.nestedContainer(keyedBy:forKey:) or decode arrays with container.decode([T].self, forKey:). This gives you full control over the transformation, including merging multiple JSON fields into one Swift property.