解码异构 JSON
处理多态且结构动态的 JSON。
解码异构 JSON 是 CoddyKit 上的免费 Swift Academy 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Swift Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Swift Academy 课程共包含 4 节课。
什么是异构 JSON
有些 API 会返回这样的数组:其中的对象具有不同结构,并通过一个 type 字段加以区分。要将这些对象解码为一种 Swift 类型,需要先检查这个判别字段。
import Foundation
let json = "[{\"type\":\"text\",\"value\":\"hi\"},{\"type\":\"number\",\"value\":42}]"
print("Each element carries a type discriminator")使用枚举建模
一种自然的 Swift 模型是带有关联值的枚举——每种 JSON 结构对应一个枚举成员。解码器会根据判别字段选择相应的成员。
import Foundation
enum Block {
case text(String)
case number(Int)
}
print("Enum models the variants")定义判别键
添加一个 CodingKeys 枚举,其中包含判别字段(这里是 type)以及您需要读取的数据键。
import Foundation
enum CodingKeys: String, CodingKey {
case type
case value
}
print(CodingKeys.type.stringValue, CodingKeys.value.stringValue)读取判别字段
在 init(from:) 中,先解码 type 字符串,然后根据它使用 switch 解码匹配的数据。
import Foundation
enum Block: Decodable {
case text(String)
case number(Int)
enum CodingKeys: String, CodingKey { case type, value }
init(from decoder: Decoder) throws {
let c = try decoder.container(keyedBy: CodingKeys.self)
let type = try c.decode(String.self, forKey: .type)
switch type {
case "text": self = .text(try c.decode(String.self, forKey: .value))
case "number": self = .number(try c.decode(Int.self, forKey: .value))
default: throw DecodingError.dataCorruptedError(forKey: .type, in: c, debugDescription: "unknown type")
}
}
}
let b = try JSONDecoder().decode(Block.self, from: "{\"type\":\"text\",\"value\":\"hi\"}".data(using: .utf8)!)
if case let .text(s) = b { print(s) }解码混合数组
枚举符合 Decodable 后,解码 [Block].self 就可以通过一次调用处理包含多种结构的整个数组。
import Foundation
enum Block: Decodable {
case text(String)
case number(Int)
enum CodingKeys: String, CodingKey { case type, value }
init(from decoder: Decoder) throws {
let c = try decoder.container(keyedBy: CodingKeys.self)
switch try c.decode(String.self, forKey: .type) {
case "text": self = .text(try c.decode(String.self, forKey: .value))
case "number": self = .number(try c.decode(Int.self, forKey: .value))
default: throw DecodingError.dataCorruptedError(forKey: .type, in: c, debugDescription: "unknown")
}
}
}
let json = "[{\"type\":\"text\",\"value\":\"hi\"},{\"type\":\"number\",\"value\":42}]"
let blocks = try JSONDecoder().decode([Block].self, from: json.data(using: .utf8)!)
print(blocks.count)对结果进行模式匹配
解码后,对枚举使用 switch,即可用类型安全的方式处理每个变体。
import Foundation
enum Block: Decodable {
case text(String)
case number(Int)
enum CodingKeys: String, CodingKey { case type, value }
init(from decoder: Decoder) throws {
let c = try decoder.container(keyedBy: CodingKeys.self)
switch try c.decode(String.self, forKey: .type) {
case "text": self = .text(try c.decode(String.self, forKey: .value))
case "number": self = .number(try c.decode(Int.self, forKey: .value))
default: throw DecodingError.dataCorruptedError(forKey: .type, in: c, debugDescription: "unknown")
}
}
}
let json = "[{\"type\":\"text\",\"value\":\"hi\"},{\"type\":\"number\",\"value\":42}]"
for b in try JSONDecoder().decode([Block].self, from: json.data(using: .utf8)!) {
switch b {
case .text(let s): print("text:", s)
case .number(let n): print("number:", n)
}
}嵌套数据
当不同变体携带更丰富的数据时,可以为每个成员解码一个嵌套的 Codable 结构体,而不是只解码单个值。
import Foundation
struct ImagePayload: Decodable { var url: String; var width: Int }
enum Block: Decodable {
case image(ImagePayload)
enum CodingKeys: String, CodingKey { case type }
init(from decoder: Decoder) throws {
let c = try decoder.container(keyedBy: CodingKeys.self)
switch try c.decode(String.self, forKey: .type) {
case "image": self = .image(try ImagePayload(from: decoder))
default: throw DecodingError.dataCorruptedError(forKey: .type, in: c, debugDescription: "unknown")
}
}
}
let json = "{\"type\":\"image\",\"url\":\"a.png\",\"width\":100}"
if case let .image(p) = try JSONDecoder().decode(Block.self, from: json.data(using: .utf8)!) {
print(p.url, p.width)
}优雅地处理未知类型
您不必在遇到未知判别字段时抛出错误,也可以将其映射到一个备用成员,这样服务器新增类型时客户端就不会崩溃。
import Foundation
enum Block: Decodable {
case text(String)
case unknown
enum CodingKeys: String, CodingKey { case type, value }
init(from decoder: Decoder) throws {
let c = try decoder.container(keyedBy: CodingKeys.self)
switch try c.decode(String.self, forKey: .type) {
case "text": self = .text(try c.decode(String.self, forKey: .value))
default: self = .unknown
}
}
}
let b = try JSONDecoder().decode(Block.self, from: "{\"type\":\"video\"}".data(using: .utf8)!)
if case .unknown = b { print("fell back to unknown") }选择合适的判别字段
判别字段应该稳定且必需。使用包含已知类型名称的 String 枚举,可以让 switch 保持穷尽,并且更易读。
import Foundation
enum Kind: String, Decodable { case text, number }
let data = "\"text\"".data(using: .utf8)!
let k = try JSONDecoder().decode(Kind.self, from: data)
print(k == .text)编码多态值
要实现往返转换,还需要实现 encode(to:):将判别字段和变体的数据一起写回。
import Foundation
enum Block: Encodable {
case text(String)
case number(Int)
enum CodingKeys: String, CodingKey { case type, value }
func encode(to encoder: Encoder) throws {
var c = encoder.container(keyedBy: CodingKeys.self)
switch self {
case .text(let s):
try c.encode("text", forKey: .type)
try c.encode(s, forKey: .value)
case .number(let n):
try c.encode("number", forKey: .type)
try c.encode(n, forKey: .value)
}
}
}
let data = try JSONEncoder().encode(Block.number(42))
print(String(data: data, encoding: .utf8)!)整合起来
完整的多态模型可以解码混合数组、根据每个变体进行处理,并重新编码为相同的结构,这是处理灵活 API 的可靠模式。
import Foundation
enum Block: Codable {
case text(String)
case number(Int)
enum CodingKeys: String, CodingKey { case type, value }
init(from d: Decoder) throws {
let c = try d.container(keyedBy: CodingKeys.self)
switch try c.decode(String.self, forKey: .type) {
case "text": self = .text(try c.decode(String.self, forKey: .value))
default: self = .number(try c.decode(Int.self, forKey: .value))
}
}
func encode(to e: Encoder) throws {
var c = e.container(keyedBy: CodingKeys.self)
switch self {
case .text(let s): try c.encode("text", forKey: .type); try c.encode(s, forKey: .value)
case .number(let n): try c.encode("number", forKey: .type); try c.encode(n, forKey: .value)
}
}
}
let blocks: [Block] = [.text("hi"), .number(7)]
let data = try JSONEncoder().encode(blocks)
print(try JSONDecoder().decode([Block].self, from: data).count)快速检查:异构 JSON
检验您对多态解码的理解。
回顾:解码异构 JSON
您学习了如何处理多态数据:
- 使用带有关联值的枚举为不同变体建模。
- 在
init(from:)中解码判别字段,然后使用 switch 构建正确的成员。 - 使用
[Enum].self解码混合数组,并对结果进行模式匹配。 - 为未知类型添加备用成员,并实现
encode(to:)以支持往返转换。
常见问题解答
「解码异构 JSON」课时是免费的吗?
是的 — 「解码异构 JSON」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Swift Academy 课程的其余内容,请升级到 CoddyKit PRO。 Swift Academy 课程共包含 4 节课。
「解码异构 JSON」这节课中我会学到什么?
处理多态且结构动态的 JSON。 你通过在浏览器中直接运行的动手代码来练习 Swift Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Swift Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Swift Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。
「解码异构 JSON」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Swift Academy 课中编写并运行代码吗?
能。每节 Swift Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。