手动实现 encode(to:) 与 init(from:)
完全控制序列化逻辑。
手动实现 encode(to:) 与 init(from:) 是 CoddyKit 上的免费 Swift Academy 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Swift Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Swift Academy 课程共包含 4 节课。
当自动合成不够用时
有时 JSON 的结构与您的属性无法对应,您可能需要进行计算转换、提供默认备用值或展平结构。这时,您需要手动实现 encode(to:) 和 init(from:)。
import Foundation
struct Temperature: Codable {
var celsius: Double
}
print("Manual coding gives full control")先声明 CodingKeys
手动编写编码逻辑要从 CodingKeys 枚举开始。键控容器会使用这些键来读取和写入值。
import Foundation
struct User {
var name: String
var age: Int
enum CodingKeys: String, CodingKey {
case name
case age
}
}
print(User.CodingKeys.name.stringValue)实现 init(from:)
init(from decoder:) 会通过 container(keyedBy:) 获取一个键控容器,然后根据各属性对应的键对其进行解码。
import Foundation
struct User: Decodable {
var name: String
var age: Int
enum CodingKeys: String, CodingKey { case name, age }
init(from decoder: Decoder) throws {
let c = try decoder.container(keyedBy: CodingKeys.self)
name = try c.decode(String.self, forKey: .name)
age = try c.decode(Int.self, forKey: .age)
}
}
let u = try JSONDecoder().decode(User.self, from: "{\"name\":\"Ada\",\"age\":36}".data(using: .utf8)!)
print(u.name, u.age)实现 encode(to:)
encode(to encoder:) 会通过 container(keyedBy:) 获取一个可变的键控容器,并使用 encode(_:forKey:) 写入每个属性。
import Foundation
struct User: Encodable {
var name: String
var age: Int
enum CodingKeys: String, CodingKey { case name, age }
func encode(to encoder: Encoder) throws {
var c = encoder.container(keyedBy: CodingKeys.self)
try c.encode(name, forKey: .name)
try c.encode(age, forKey: .age)
}
}
let data = try JSONEncoder().encode(User(name: "Ada", age: 36))
print(String(data: data, encoding: .utf8)!)手动实现完整 Codable
同时实现这两个方法后,该类型就具备了完整的 Codable 能力,并且可以在编码和解码两端使用自定义逻辑。
import Foundation
struct Point: Codable {
var x: Int
var y: Int
enum CodingKeys: String, CodingKey { case x, y }
init(x: Int, y: Int) { self.x = x; self.y = y }
init(from d: Decoder) throws {
let c = try d.container(keyedBy: CodingKeys.self)
x = try c.decode(Int.self, forKey: .x)
y = try c.decode(Int.self, forKey: .y)
}
func encode(to e: Encoder) throws {
var c = e.container(keyedBy: CodingKeys.self)
try c.encode(x, forKey: .x)
try c.encode(y, forKey: .y)
}
}
let back = try JSONDecoder().decode(Point.self, from: try JSONEncoder().encode(Point(x: 1, y: 2)))
print(back.x, back.y)提供默认值
将 decodeIfPresent 与空值合并运算符结合使用,可以在键缺失时提供默认值,而不是抛出错误。
import Foundation
struct Settings: Decodable {
var theme: String
enum CodingKeys: String, CodingKey { case theme }
init(from decoder: Decoder) throws {
let c = try decoder.container(keyedBy: CodingKeys.self)
theme = try c.decodeIfPresent(String.self, forKey: .theme) ?? "light"
}
}
let s = try JSONDecoder().decode(Settings.self, from: "{}".data(using: .utf8)!)
print(s.theme)转换值
手动解码可以将原始 JSON 转换为更丰富的表示形式——在这里,华氏温度输入会以摄氏温度存储。
import Foundation
struct Reading: Decodable {
var celsius: Double
enum CodingKeys: String, CodingKey { case fahrenheit }
init(from decoder: Decoder) throws {
let c = try decoder.container(keyedBy: CodingKeys.self)
let f = try c.decode(Double.self, forKey: .fahrenheit)
celsius = (f - 32) * 5 / 9
}
}
let r = try JSONDecoder().decode(Reading.self, from: "{\"fahrenheit\":212}".data(using: .utf8)!)
print(r.celsius)展平嵌套 JSON
您可以使用 nestedContainer(keyedBy:forKey:) 读取嵌套容器,并将其中的值提升到扁平的 Swift 结构体上。
import Foundation
struct Profile: Decodable {
var city: String
enum Keys: String, CodingKey { case address }
enum AddressKeys: String, CodingKey { case city }
init(from decoder: Decoder) throws {
let c = try decoder.container(keyedBy: Keys.self)
let a = try c.nestedContainer(keyedBy: AddressKeys.self, forKey: .address)
city = try a.decode(String.self, forKey: .city)
}
}
let json = "{\"address\":{\"city\":\"Rome\"}}"
let p = try JSONDecoder().decode(Profile.self, from: json.data(using: .utf8)!)
print(p.city)编码到嵌套容器
编码也可以执行相反的操作:使用 nestedContainer(keyedBy:forKey:),将扁平结构体写出为嵌套的 JSON 对象。
import Foundation
struct Profile: Encodable {
var city: String
enum Keys: String, CodingKey { case address }
enum AddressKeys: String, CodingKey { case city }
func encode(to encoder: Encoder) throws {
var c = encoder.container(keyedBy: Keys.self)
var a = c.nestedContainer(keyedBy: AddressKeys.self, forKey: .address)
try a.encode(city, forKey: .city)
}
}
let data = try JSONEncoder().encode(Profile(city: "Rome"))
print(String(data: data, encoding: .utf8)!)在解码过程中进行验证
由于 init(from:) 本质上就是一个初始化器,因此您可以验证值,并在值超出范围时抛出自定义错误。
import Foundation
struct Score: Decodable {
var value: Int
enum CodingKeys: String, CodingKey { case value }
init(from decoder: Decoder) throws {
let c = try decoder.container(keyedBy: CodingKeys.self)
let v = try c.decode(Int.self, forKey: .value)
guard (0...100).contains(v) else {
throw DecodingError.dataCorruptedError(forKey: .value, in: c, debugDescription: "out of range")
}
value = v
}
}
let s = try JSONDecoder().decode(Score.self, from: "{\"value\":80}".data(using: .utf8)!)
print(s.value)混合使用手动实现与自动合成
一种类型可以只手动实现一个方法,让编译器自动合成另一个方法——例如自定义 init(from:),同时使用自动合成的编码实现。
import Foundation
struct Flag: Codable {
var on: Bool
enum CodingKeys: String, CodingKey { case on }
init(from decoder: Decoder) throws {
let c = try decoder.container(keyedBy: CodingKeys.self)
on = (try? c.decode(Bool.self, forKey: .on)) ?? false
}
init(on: Bool) { self.on = on }
}
let data = try JSONEncoder().encode(Flag(on: true))
print(String(data: data, encoding: .utf8)!)快速检查:手动编码
检验您对手写 Codable 方法的理解。
回顾:手动实现 encode(to:) 和 init(from:)
您学习了如何完全控制编码过程:
- 声明
CodingKeys,然后实现init(from:)和encode(to:)。 - 使用
container(keyedBy:)和encode/decode(_:forKey:)。 decodeIfPresent加上??可以提供默认值;nestedContainer可以展平或嵌套 JSON。- 您可以进行验证、转换,并将手动实现的方法与自动合成的方法混合使用。
用 AI 导师学习 Swift — 免费
在浏览器中编写并运行真实代码,获得全天候 AI 导师的即时帮助,并在网页或应用中继续学习。
- 课程
- 122
- 课程
- 409
常见问题解答
「手动实现 encode(to:) 与 init(from:)」课时是免费的吗?
是的 — 「手动实现 encode(to:) 与 init(from:)」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Swift Academy 课程的其余内容,请升级到 CoddyKit PRO。 Swift Academy 课程共包含 4 节课。
「手动实现 encode(to:) 与 init(from:)」这节课中我会学到什么?
完全控制序列化逻辑。 你通过在浏览器中直接运行的动手代码来练习 Swift Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Swift Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Swift Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。
「手动实现 encode(to:) 与 init(from:)」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Swift Academy 课中编写并运行代码吗?
能。每节 Swift Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。
此课程中的所有课时
- 使用 CodingKeys 重命名
- 键与日期解码策略
- 手动实现 encode(to:) 与 init(from:)
- 解码异构 JSON