swift-codable

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Swift Codable

Swift Codable

Encode and decode Swift types using
Codable
(
Encodable & Decodable
) with
JSONEncoder
,
JSONDecoder
, and related APIs. Targets Swift 6.2 / iOS 26+.
使用
Codable
Encodable & Decodable
)结合
JSONEncoder
JSONDecoder
及相关API对Swift类型进行编解码。适配Swift 6.2 / iOS 26+。

Contents

目录

Basic Conformance

基础一致性

When all stored properties are themselves
Codable
, the compiler synthesizes conformance automatically:
swift
struct User: Codable {
    let id: Int
    let name: String
    let email: String
    let isVerified: Bool
}

let user = try JSONDecoder().decode(User.self, from: jsonData)
let encoded = try JSONEncoder().encode(user)
Prefer
Decodable
for read-only API responses and
Encodable
for write-only. Use
Codable
only when both directions are required.
当所有存储属性本身都遵循
Codable
时,编译器会自动合成一致性实现:
swift
struct User: Codable {
    let id: Int
    let name: String
    let email: String
    let isVerified: Bool
}

let user = try JSONDecoder().decode(User.self, from: jsonData)
let encoded = try JSONEncoder().encode(user)
对于只读的API响应,优先使用
Decodable
;对于只写场景,优先使用
Encodable
。仅当需要双向编解码时才使用
Codable

Custom CodingKeys

自定义CodingKeys

Rename JSON keys without writing a custom decoder by declaring a
CodingKeys
enum:
swift
struct Product: Codable {
    let id: Int
    let displayName: String
    let imageURL: URL
    let priceInCents: Int

    enum CodingKeys: String, CodingKey {
        case id
        case displayName = "display_name"
        case imageURL = "image_url"
        case priceInCents = "price_in_cents"
    }
}
Every stored property must appear in the enum. Omitting a property from
CodingKeys
excludes it from encoding/decoding -- provide a default value or compute it separately.
通过声明
CodingKeys
枚举,无需编写自定义解码器即可重命名JSON键:
swift
struct Product: Codable {
    let id: Int
    let displayName: String
    let imageURL: URL
    let priceInCents: Int

    enum CodingKeys: String, CodingKey {
        case id
        case displayName = "display_name"
        case imageURL = "image_url"
        case priceInCents = "price_in_cents"
    }
}
所有存储属性都必须出现在该枚举中。如果某个属性未在
CodingKeys
中列出,则会被排除在编解码流程之外——此时需要为其提供默认值或单独计算。

Custom Decoding and Encoding

自定义解码与编码

Override
init(from:)
and
encode(to:)
for transformations the synthesized conformance cannot handle:
swift
struct Event: Codable {
    let name: String
    let timestamp: Date
    let tags: [String]

    enum CodingKeys: String, CodingKey {
        case name, timestamp, tags
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        name = try container.decode(String.self, forKey: .name)
        // Decode Unix timestamp as Double, convert to Date
        let epoch = try container.decode(Double.self, forKey: .timestamp)
        timestamp = Date(timeIntervalSince1970: epoch)
        // Default to empty array when key is missing
        tags = try container.decodeIfPresent([String].self, forKey: .tags) ?? []
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(name, forKey: .name)
        try container.encode(timestamp.timeIntervalSince1970, forKey: .timestamp)
        try container.encode(tags, forKey: .tags)
    }
}
当自动合成的一致性无法满足需求时,重写
init(from:)
encode(to:)
方法进行转换:
swift
struct Event: Codable {
    let name: String
    let timestamp: Date
    let tags: [String]

    enum CodingKeys: String, CodingKey {
        case name, timestamp, tags
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        name = try container.decode(String.self, forKey: .name)
        // 将Unix时间戳解码为Double,再转换为Date
        let epoch = try container.decode(Double.self, forKey: .timestamp)
        timestamp = Date(timeIntervalSince1970: epoch)
        // 当键缺失时默认返回空数组
        tags = try container.decodeIfPresent([String].self, forKey: .tags) ?? []
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(name, forKey: .name)
        try container.encode(timestamp.timeIntervalSince1970, forKey: .timestamp)
        try container.encode(tags, forKey: .tags)
    }
}

Nested and Flattened Containers

嵌套与扁平化容器

Use
nestedContainer(keyedBy:forKey:)
to navigate and flatten nested JSON:
swift
// JSON: { "id": 1, "location": { "lat": 37.7749, "lng": -122.4194 } }
struct Place: Decodable {
    let id: Int
    let latitude: Double
    let longitude: Double

    enum CodingKeys: String, CodingKey { case id, location }
    enum LocationKeys: String, CodingKey { case lat, lng }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        id = try container.decode(Int.self, forKey: .id)
        let location = try container.nestedContainer(
            keyedBy: LocationKeys.self, forKey: .location)
        latitude = try location.decode(Double.self, forKey: .lat)
        longitude = try location.decode(Double.self, forKey: .lng)
    }
}
Chain multiple
nestedContainer
calls to flatten deeply nested structures. Also use
nestedUnkeyedContainer(forKey:)
for nested arrays.
使用
nestedContainer(keyedBy:forKey:)
来遍历并扁平化嵌套JSON:
swift
// JSON: { "id": 1, "location": { "lat": 37.7749, "lng": -122.4194 } }
struct Place: Decodable {
    let id: Int
    let latitude: Double
    let longitude: Double

    enum CodingKeys: String, CodingKey { case id, location }
    enum LocationKeys: String, CodingKey { case lat, lng }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        id = try container.decode(Int.self, forKey: .id)
        let location = try container.nestedContainer(
            keyedBy: LocationKeys.self, forKey: .location)
        latitude = try location.decode(Double.self, forKey: .lat)
        longitude = try location.decode(Double.self, forKey: .lng)
    }
}
可以链式调用多个
nestedContainer
来扁平化深层嵌套结构。也可使用
nestedUnkeyedContainer(forKey:)
处理嵌套数组。

Heterogeneous Arrays

异构数组

Decode arrays of mixed types using a discriminator field:
swift
// JSON: [{"type":"text","content":"Hello"},{"type":"image","url":"pic.jpg"}]
enum ContentBlock: Decodable {
    case text(String)
    case image(URL)

    enum CodingKeys: String, CodingKey { case type, content, url }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let type = try container.decode(String.self, forKey: .type)
        switch type {
        case "text":
            let content = try container.decode(String.self, forKey: .content)
            self = .text(content)
        case "image":
            let url = try container.decode(URL.self, forKey: .url)
            self = .image(url)
        default:
            throw DecodingError.dataCorruptedError(
                forKey: .type, in: container,
                debugDescription: "Unknown type: \(type)")
        }
    }
}

let blocks = try JSONDecoder().decode([ContentBlock].self, from: jsonData)
使用鉴别器字段解码混合类型的数组:
swift
// JSON: [{"type":"text","content":"Hello"},{"type":"image","url":"pic.jpg"}]
enum ContentBlock: Decodable {
    case text(String)
    case image(URL)

    enum CodingKeys: String, CodingKey { case type, content, url }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let type = try container.decode(String.self, forKey: .type)
        switch type {
        case "text":
            let content = try container.decode(String.self, forKey: .content)
            self = .text(content)
        case "image":
            let url = try container.decode(URL.self, forKey: .url)
            self = .image(url)
        default:
            throw DecodingError.dataCorruptedError(
                forKey: .type, in: container,
                debugDescription: "Unknown type: \(type)")
        }
    }
}

let blocks = try JSONDecoder().decode([ContentBlock].self, from: jsonData)

Date Decoding Strategies

日期解码策略

Configure
JSONDecoder.dateDecodingStrategy
to match your API:
swift
let decoder = JSONDecoder()

// ISO 8601 (e.g., "2024-03-15T10:30:00Z")
decoder.dateDecodingStrategy = .iso8601

// Unix timestamp in seconds (e.g., 1710499800)
decoder.dateDecodingStrategy = .secondsSince1970

// Custom DateFormatter
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(secondsFromGMT: 0)
decoder.dateDecodingStrategy = .formatted(formatter)

// Custom closure for multiple formats
decoder.dateDecodingStrategy = .custom { decoder in
    let container = try decoder.singleValueContainer()
    let string = try container.decode(String.self)
    if let date = ISO8601DateFormatter().date(from: string) { return date }
    throw DecodingError.dataCorruptedError(
        in: container, debugDescription: "Cannot decode date: \(string)")
}
Set the matching strategy on
JSONEncoder
:
encoder.dateEncodingStrategy = .iso8601
配置
JSONDecoder.dateDecodingStrategy
以匹配你的API格式:
swift
let decoder = JSONDecoder()

// ISO 8601格式(例如:"2024-03-15T10:30:00Z")
decoder.dateDecodingStrategy = .iso8601

// 以秒为单位的Unix时间戳(例如:1710499800)
decoder.dateDecodingStrategy = .secondsSince1970

// 自定义DateFormatter
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(secondsFromGMT: 0)
decoder.dateDecodingStrategy = .formatted(formatter)

// 自定义闭包支持多种格式
decoder.dateDecodingStrategy = .custom { decoder in
    let container = try decoder.singleValueContainer()
    let string = try container.decode(String.self)
    if let date = ISO8601DateFormatter().date(from: string) { return date }
    throw DecodingError.dataCorruptedError(
        in: container, debugDescription: "Cannot decode date: \(string)")
}
JSONEncoder
上设置对应的策略:
encoder.dateEncodingStrategy = .iso8601

Data and Key Strategies

数据与键策略

swift
let decoder = JSONDecoder()
decoder.dataDecodingStrategy = .base64           // Base64-encoded Data fields
decoder.keyDecodingStrategy = .convertFromSnakeCase  // snake_case -> camelCase
// {"user_name": "Alice"} maps to `var userName: String` -- no CodingKeys needed

let encoder = JSONEncoder()
encoder.dataEncodingStrategy = .base64
encoder.keyEncodingStrategy = .convertToSnakeCase
swift
let decoder = JSONDecoder()
decoder.dataDecodingStrategy = .base64           // Base64编码的Data字段
decoder.keyDecodingStrategy = .convertFromSnakeCase  // snake_case 转换为 camelCase
// {"user_name": "Alice"} 会映射到 `var userName: String` —— 无需CodingKeys

let encoder = JSONEncoder()
encoder.dataEncodingStrategy = .base64
encoder.keyEncodingStrategy = .convertToSnakeCase

Lossy Array Decoding

容错数组解码

By default, one invalid element fails the entire array. Use a wrapper to skip invalid elements:
swift
struct LossyArray<Element: Decodable>: Decodable {
    let elements: [Element]

    init(from decoder: Decoder) throws {
        var container = try decoder.unkeyedContainer()
        var elements: [Element] = []
        while !container.isAtEnd {
            if let element = try? container.decode(Element.self) {
                elements.append(element)
            } else {
                _ = try? container.decode(AnyCodableValue.self) // advance past bad element
            }
        }
        self.elements = elements
    }
}
private struct AnyCodableValue: Decodable {}
默认情况下,数组中只要有一个无效元素就会导致整个解码失败。使用包装器来跳过无效元素:
swift
struct LossyArray<Element: Decodable>: Decodable {
    let elements: [Element]

    init(from decoder: Decoder) throws {
        var container = try decoder.unkeyedContainer()
        var elements: [Element] = []
        while !container.isAtEnd {
            if let element = try? container.decode(Element.self) {
                elements.append(element)
            } else {
                _ = try? container.decode(AnyCodableValue.self) // 跳过无效元素
            }
        }
        self.elements = elements
    }
}
private struct AnyCodableValue: Decodable {}

Single Value Containers

单值容器

Wrap primitives for type safety using
singleValueContainer()
:
swift
struct UserID: Codable, Hashable {
    let rawValue: String

    init(_ rawValue: String) { self.rawValue = rawValue }

    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        rawValue = try container.decode(String.self)
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        try container.encode(rawValue)
    }
}
// JSON: "usr_abc123" decodes directly to UserID
使用
singleValueContainer()
包装基本类型以实现类型安全:
swift
struct UserID: Codable, Hashable {
    let rawValue: String

    init(_ rawValue: String) { self.rawValue = rawValue }

    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        rawValue = try container.decode(String.self)
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        try container.encode(rawValue)
    }
}
// JSON: "usr_abc123" 可直接解码为 UserID

Default Values for Missing Keys

缺失键的默认值

Use
decodeIfPresent
with nil-coalescing to provide defaults:
swift
struct Settings: Decodable {
    let theme: String
    let fontSize: Int
    let notificationsEnabled: Bool

    enum CodingKeys: String, CodingKey {
        case theme, fontSize = "font_size"
        case notificationsEnabled = "notifications_enabled"
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        theme = try container.decodeIfPresent(String.self, forKey: .theme) ?? "system"
        fontSize = try container.decodeIfPresent(Int.self, forKey: .fontSize) ?? 16
        notificationsEnabled = try container.decodeIfPresent(
            Bool.self, forKey: .notificationsEnabled) ?? true
    }
}
结合
decodeIfPresent
和空合运算符为缺失的键提供默认值:
swift
struct Settings: Decodable {
    let theme: String
    let fontSize: Int
    let notificationsEnabled: Bool

    enum CodingKeys: String, CodingKey {
        case theme, fontSize = "font_size"
        case notificationsEnabled = "notifications_enabled"
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        theme = try container.decodeIfPresent(String.self, forKey: .theme) ?? "system"
        fontSize = try container.decodeIfPresent(Int.self, forKey: .fontSize) ?? 16
        notificationsEnabled = try container.decodeIfPresent(
            Bool.self, forKey: .notificationsEnabled) ?? true
    }
}

Encoder and Decoder Configuration

编码器与解码器配置

swift
let encoder = JSONEncoder()
encoder.outputFormatting = [.prettyPrinted, .sortedKeys, .withoutEscapingSlashes]

// Non-conforming floats (NaN, Infinity are not valid JSON)
encoder.nonConformingFloatEncodingStrategy = .convertToString(
    positiveInfinity: "Infinity", negativeInfinity: "-Infinity", nan: "NaN")
decoder.nonConformingFloatDecodingStrategy = .convertFromString(
    positiveInfinity: "Infinity", negativeInfinity: "-Infinity", nan: "NaN")
swift
let encoder = JSONEncoder()
encoder.outputFormatting = [.prettyPrinted, .sortedKeys, .withoutEscapingSlashes]

// 非标准浮点数(NaN、Infinity不是合法JSON)
encoder.nonConformingFloatEncodingStrategy = .convertToString(
    positiveInfinity: "Infinity", negativeInfinity: "-Infinity", nan: "NaN")
decoder.nonConformingFloatDecodingStrategy = .convertFromString(
    positiveInfinity: "Infinity", negativeInfinity: "-Infinity", nan: "NaN")

PropertyListEncoder / PropertyListDecoder

PropertyListEncoder / PropertyListDecoder

swift
let plistEncoder = PropertyListEncoder()
plistEncoder.outputFormat = .xml  // or .binary
let data = try plistEncoder.encode(settings)
let decoded = try PropertyListDecoder().decode(Settings.self, from: data)
swift
let plistEncoder = PropertyListEncoder()
plistEncoder.outputFormat = .xml  // 或 .binary
let data = try plistEncoder.encode(settings)
let decoded = try PropertyListDecoder().decode(Settings.self, from: data)

Codable with URLSession

Codable与URLSession

swift
func fetchUser(id: Int) async throws -> User {
    let url = URL(string: "https://api.example.com/users/\(id)")!
    let (data, response) = try await URLSession.shared.data(from: url)
    guard let http = response as? HTTPURLResponse,
          (200...299).contains(http.statusCode) else {
        throw APIError.invalidResponse
    }
    let decoder = JSONDecoder()
    decoder.keyDecodingStrategy = .convertFromSnakeCase
    decoder.dateDecodingStrategy = .iso8601
    return try decoder.decode(User.self, from: data)
}

// Generic API envelope for wrapped responses
struct APIResponse<T: Decodable>: Decodable {
    let data: T
    let meta: Meta?
    struct Meta: Decodable { let page: Int; let totalPages: Int }
}
let users = try decoder.decode(APIResponse<[User]>.self, from: data).data
swift
func fetchUser(id: Int) async throws -> User {
    let url = URL(string: "https://api.example.com/users/\(id)")!
    let (data, response) = try await URLSession.shared.data(from: url)
    guard let http = response as? HTTPURLResponse,
          (200...299).contains(http.statusCode) else {
        throw APIError.invalidResponse
    }
    let decoder = JSONDecoder()
    decoder.keyDecodingStrategy = .convertFromSnakeCase
    decoder.dateDecodingStrategy = .iso8601
    return try decoder.decode(User.self, from: data)
}

// 用于包装响应的通用API信封
struct APIResponse<T: Decodable>: Decodable {
    let data: T
    let meta: Meta?
    struct Meta: Decodable { let page: Int; let totalPages: Int }
}
let users = try decoder.decode(APIResponse<[User]>.self, from: data).data

Codable with SwiftData

Codable与SwiftData

Codable
structs work as composite attributes in SwiftData models. In iOS 18+, SwiftData natively supports them without explicit
@Attribute(.transformable)
:
swift
struct Address: Codable {
    var street: String
    var city: String
    var zipCode: String
}

@Model class Contact {
    var name: String
    var address: Address?  // Codable struct stored as composite attribute
    init(name: String, address: Address? = nil) {
        self.name = name; self.address = address
    }
}
Codable
结构体可作为SwiftData模型中的复合属性。在iOS 18+中,SwiftData原生支持它们,无需显式使用
@Attribute(.transformable)
swift
struct Address: Codable {
    var street: String
    var city: String
    var zipCode: String
}

@Model class Contact {
    var name: String
    var address: Address?  // Codable结构体作为复合属性存储
    init(name: String, address: Address? = nil) {
        self.name = name; self.address = address
    }
}

Codable with UserDefaults

Codable与UserDefaults

Store
Codable
values via
RawRepresentable
for
@AppStorage
:
swift
struct UserPreferences: Codable {
    var showOnboarding: Bool = true
    var accentColor: String = "blue"
}

extension UserPreferences: RawRepresentable {
    init?(rawValue: String) {
        guard let data = rawValue.data(using: .utf8),
              let decoded = try? JSONDecoder().decode(Self.self, from: data)
        else { return nil }
        self = decoded
    }
    var rawValue: String {
        guard let data = try? JSONEncoder().encode(self),
              let string = String(data: data, encoding: .utf8)
        else { return "{}" }
        return string
    }
}

struct SettingsView: View {
    @AppStorage("userPrefs") private var prefs = UserPreferences()
    var body: some View {
        Toggle("Show Onboarding", isOn: $prefs.showOnboarding)
    }
}
通过
RawRepresentable
Codable
值存储到
@AppStorage
swift
struct UserPreferences: Codable {
    var showOnboarding: Bool = true
    var accentColor: String = "blue"
}

extension UserPreferences: RawRepresentable {
    init?(rawValue: String) {
        guard let data = rawValue.data(using: .utf8),
              let decoded = try? JSONDecoder().decode(Self.self, from: data)
        else { return nil }
        self = decoded
    }
    var rawValue: String {
        guard let data = try? JSONEncoder().encode(self),
              let string = String(data: data, encoding: .utf8)
        else { return "{}" }
        return string
    }
}

struct SettingsView: View {
    @AppStorage("userPrefs") private var prefs = UserPreferences()
    var body: some View {
        Toggle("Show Onboarding", isOn: $prefs.showOnboarding)
    }
}

Common Mistakes

常见错误

1. Not handling missing optional keys:
swift
// DON'T -- crashes if key is absent
let value = try container.decode(String.self, forKey: .bio)
// DO -- returns nil for missing keys
let value = try container.decodeIfPresent(String.self, forKey: .bio) ?? ""
2. Failing entire array when one element is invalid:
swift
// DON'T -- one bad element kills the whole decode
let items = try container.decode([Item].self, forKey: .items)
// DO -- use LossyArray or decode elements individually
let items = try container.decode(LossyArray<Item>.self, forKey: .items).elements
3. Date strategy mismatch:
swift
// DON'T -- default strategy expects Double, but API sends ISO string
let decoder = JSONDecoder()  // dateDecodingStrategy defaults to .deferredToDate
// DO -- set strategy to match your API format
decoder.dateDecodingStrategy = .iso8601
4. Force-unwrapping decoded optionals:
swift
// DON'T
let user = try? decoder.decode(User.self, from: data)
print(user!.name)
// DO
guard let user = try? decoder.decode(User.self, from: data) else { return }
5. Using Codable when only Decodable is needed:
swift
// DON'T -- unnecessarily constrains the type to also be Encodable
struct APIResponse: Codable { let id: Int; let message: String }
// DO -- use Decodable for read-only API responses
struct APIResponse: Decodable { let id: Int; let message: String }
6. Manual CodingKeys for simple snake_case APIs:
swift
// DON'T -- verbose boilerplate for every model
enum CodingKeys: String, CodingKey {
    case userName = "user_name"
    case avatarUrl = "avatar_url"
}
// DO -- configure once on the decoder
decoder.keyDecodingStrategy = .convertFromSnakeCase
1. 未处理可选键的缺失情况:
swift
// 错误示例 —— 当键不存在时会崩溃
let value = try container.decode(String.self, forKey: .bio)
// 正确示例 —— 键缺失时返回nil
let value = try container.decodeIfPresent(String.self, forKey: .bio) ?? ""
2. 单个无效元素导致整个数组解码失败:
swift
// 错误示例 —— 一个无效元素导致整个解码失败
let items = try container.decode([Item].self, forKey: .items)
// 正确示例 —— 使用LossyArray或逐个解码元素
let items = try container.decode(LossyArray<Item>.self, forKey: .items).elements
3. 日期策略不匹配:
swift
// 错误示例 —— 默认策略期望Double类型,但API返回ISO字符串
let decoder = JSONDecoder()  // dateDecodingStrategy默认是.deferredToDate
// 正确示例 —— 设置与API格式匹配的策略
decoder.dateDecodingStrategy = .iso8601
4. 强制解包解码后的可选值:
swift
// 错误示例
let user = try? decoder.decode(User.self, from: data)
print(user!.name)
// 正确示例
guard let user = try? decoder.decode(User.self, from: data) else { return }
5. 仅需要Decodable时使用Codable:
swift
// 错误示例 —— 不必要地将类型约束为同时支持Encodable
struct APIResponse: Codable { let id: Int; let message: String }
// 正确示例 —— 只读API响应使用Decodable
struct APIResponse: Decodable { let id: Int; let message: String }
6. 为简单的snake_case API手动编写CodingKeys:
swift
// 错误示例 —— 每个模型都需要冗余的模板代码
enum CodingKeys: String, CodingKey {
    case userName = "user_name"
    case avatarUrl = "avatar_url"
}
// 正确示例 —— 在解码器上配置一次即可
decoder.keyDecodingStrategy = .convertFromSnakeCase

Review Checklist

检查清单

  • Types conform to
    Decodable
    only when encoding is not needed
  • decodeIfPresent
    used with defaults for optional or missing keys
  • keyDecodingStrategy = .convertFromSnakeCase
    used instead of manual CodingKeys for simple snake_case APIs
  • dateDecodingStrategy
    matches the API date format
  • Arrays of unreliable data use lossy decoding to skip invalid elements
  • Custom
    init(from:)
    validates and transforms data instead of post-decode fixups
  • JSONEncoder.outputFormatting
    includes
    .sortedKeys
    for deterministic test output
  • Wrapper types (UserID, etc.) use
    singleValueContainer
    for clean JSON
  • Generic
    APIResponse<T>
    wrapper used for consistent API envelope handling
  • No force-unwrapping of decoded values
  • @AppStorage
    Codable types conform to
    RawRepresentable
  • SwiftData composite attributes use
    Codable
    structs
  • 仅当不需要编码时,类型才遵循
    Decodable
  • 对可选或缺失的键使用
    decodeIfPresent
    并提供默认值
  • 对于简单的snake_case API,使用
    keyDecodingStrategy = .convertFromSnakeCase
    而非手动编写CodingKeys
  • dateDecodingStrategy
    与API的日期格式匹配
  • 不可靠数据的数组使用容错解码来跳过无效元素
  • 自定义
    init(from:)
    在解码时验证并转换数据,而非解码后再修复
  • JSONEncoder.outputFormatting
    包含
    .sortedKeys
    以实现确定性的测试输出
  • 包装类型(如UserID等)使用
    singleValueContainer
    以生成简洁的JSON
  • 使用通用的
    APIResponse<T>
    包装器处理一致的API信封
  • 不对解码后的值进行强制解包
  • 用于
    @AppStorage
    的Codable类型遵循
    RawRepresentable
  • SwiftData复合属性使用
    Codable
    结构体

References

参考资料