swift-codable
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSwift Codable
Swift Codable
Encode and decode Swift types using () with
, , and related APIs. Targets Swift 6.2 / iOS 26+.
CodableEncodable & DecodableJSONEncoderJSONDecoder使用()结合、及相关API对Swift类型进行编解码。适配Swift 6.2 / iOS 26+。
CodableEncodable & DecodableJSONEncoderJSONDecoderContents
目录
- Basic Conformance
- Custom CodingKeys
- Custom Decoding and Encoding
- Nested and Flattened Containers
- Heterogeneous Arrays
- Date Decoding Strategies
- Data and Key Strategies
- Lossy Array Decoding
- Single Value Containers
- Default Values for Missing Keys
- Encoder and Decoder Configuration
- Codable with URLSession
- Codable with SwiftData
- Codable with UserDefaults
- Common Mistakes
- Review Checklist
- References
Basic Conformance
基础一致性
When all stored properties are themselves , the compiler synthesizes
conformance automatically:
Codableswift
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 for read-only API responses and for write-only.
Use only when both directions are required.
DecodableEncodableCodable当所有存储属性本身都遵循时,编译器会自动合成一致性实现:
Codableswift
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响应,优先使用;对于只写场景,优先使用。仅当需要双向编解码时才使用。
DecodableEncodableCodableCustom CodingKeys
自定义CodingKeys
Rename JSON keys without writing a custom decoder by declaring a
enum:
CodingKeysswift
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
excludes it from encoding/decoding -- provide a default value or
compute it separately.
CodingKeys通过声明枚举,无需编写自定义解码器即可重命名JSON键:
CodingKeysswift
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"
}
}所有存储属性都必须出现在该枚举中。如果某个属性未在中列出,则会被排除在编解码流程之外——此时需要为其提供默认值或单独计算。
CodingKeysCustom Decoding and Encoding
自定义解码与编码
Override and for transformations the synthesized
conformance cannot handle:
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)
// 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 to navigate and flatten nested JSON:
nestedContainer(keyedBy:forKey:)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 calls to flatten deeply nested structures.
Also use for nested arrays.
nestedContainernestedUnkeyedContainer(forKey:)使用来遍历并扁平化嵌套JSON:
nestedContainer(keyedBy:forKey:)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)
}
}可以链式调用多个来扁平化深层嵌套结构。也可使用处理嵌套数组。
nestedContainernestedUnkeyedContainer(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 to match your API:
JSONDecoder.dateDecodingStrategyswift
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 :
JSONEncoderencoder.dateEncodingStrategy = .iso8601配置以匹配你的API格式:
JSONDecoder.dateDecodingStrategyswift
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)")
}在上设置对应的策略:
JSONEncoderencoder.dateEncodingStrategy = .iso8601Data 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 = .convertToSnakeCaseswift
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 = .convertToSnakeCaseLossy 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" 可直接解码为 UserIDDefault Values for Missing Keys
缺失键的默认值
Use with nil-coalescing to provide defaults:
decodeIfPresentswift
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
}
}结合和空合运算符为缺失的键提供默认值:
decodeIfPresentswift
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).dataswift
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).dataCodable with SwiftData
Codable与SwiftData
Codable@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@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 values via for :
CodableRawRepresentable@AppStorageswift
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)
}
}通过将值存储到:
RawRepresentableCodable@AppStorageswift
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).elements3. 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 = .iso86014. 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 = .convertFromSnakeCase1. 未处理可选键的缺失情况:
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).elements3. 日期策略不匹配:
swift
// 错误示例 —— 默认策略期望Double类型,但API返回ISO字符串
let decoder = JSONDecoder() // dateDecodingStrategy默认是.deferredToDate
// 正确示例 —— 设置与API格式匹配的策略
decoder.dateDecodingStrategy = .iso86014. 强制解包解码后的可选值:
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 = .convertFromSnakeCaseReview Checklist
检查清单
- Types conform to only when encoding is not needed
Decodable - used with defaults for optional or missing keys
decodeIfPresent - used instead of manual CodingKeys for simple snake_case APIs
keyDecodingStrategy = .convertFromSnakeCase - matches the API date format
dateDecodingStrategy - Arrays of unreliable data use lossy decoding to skip invalid elements
- Custom validates and transforms data instead of post-decode fixups
init(from:) - includes
JSONEncoder.outputFormattingfor deterministic test output.sortedKeys - Wrapper types (UserID, etc.) use for clean JSON
singleValueContainer - Generic wrapper used for consistent API envelope handling
APIResponse<T> - No force-unwrapping of decoded values
- Codable types conform to
@AppStorageRawRepresentable - SwiftData composite attributes use structs
Codable
- 仅当不需要编码时,类型才遵循
Decodable - 对可选或缺失的键使用并提供默认值
decodeIfPresent - 对于简单的snake_case API,使用而非手动编写CodingKeys
keyDecodingStrategy = .convertFromSnakeCase - 与API的日期格式匹配
dateDecodingStrategy - 不可靠数据的数组使用容错解码来跳过无效元素
- 自定义在解码时验证并转换数据,而非解码后再修复
init(from:) - 包含
JSONEncoder.outputFormatting以实现确定性的测试输出.sortedKeys - 包装类型(如UserID等)使用以生成简洁的JSON
singleValueContainer - 使用通用的包装器处理一致的API信封
APIResponse<T> - 不对解码后的值进行强制解包
- 用于的Codable类型遵循
@AppStorageRawRepresentable - SwiftData复合属性使用结构体
Codable
References
参考资料
- Codable -- protocol combining Encodable and Decodable
- JSONDecoder -- decodes JSON data into Codable types
- JSONEncoder -- encodes Codable types as JSON data
- CodingKey -- protocol for encoding/decoding keys
- Encoding and Decoding Custom Types -- Apple guide on custom Codable conformance
- Using JSON with Custom Types -- Apple sample code for JSON patterns
- Codable —— 结合Encodable和Decodable的协议
- JSONDecoder —— 将JSON数据解码为Codable类型
- JSONEncoder —— 将Codable类型编码为JSON数据
- CodingKey —— 编解码键的协议
- Encoding and Decoding Custom Types —— Apple官方的自定义Codable一致性指南
- Using JSON with Custom Types —— Apple官方的JSON模式示例代码