Swift Academy · 课时

手动实现 encode(to:) 与 init(from:)

完全控制序列化逻辑。

第 3 / 4 课13 个步骤

手动实现 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 反馈 — 无需本地设置。

此课程中的所有课时

  1. 使用 CodingKeys 重命名
  2. 键与日期解码策略
  3. 手动实现 encode(to:) 与 init(from:)
  4. 解码异构 JSON
← 返回 Swift Academy