你是否曾经从 API 收到过类似这样的响应,并希望提取和展平你关心的值?(这是一个来自 GitHub GraphQL API 的真实响应,仅更改了实际值)
{
"data": {
"node": {
"content": {
"__typename": "Example type",
"title": "Example title"
},
"fieldValues": {
"nodes": [
{},
{},
{
"name": "Example node name",
"field": {
"name": "Example field name"
}
}
]
}
}
}
}
DeepCodable
让你可以在 Swift 中轻松实现这一点,同时保持类型安全,这得益于结果构建器、键路径和属性包装器的魔力
import DeepCodable
struct GithubGraphqlResponse: DeepDecodable {
static let codingTree = CodingTree {
Key("data") {
Key("node") {
Key("content") {
Key("__typename", containing: \._type)
Key("title", containing: \._title)
}
Key("fieldValues") {
Key("nodes", containing: \._nodes)
}
}
}
}
struct Node: DeepDecodable {
static let codingTree = CodingTree {
Key("name", containing: \._name)
Key("field", "name", containing: \._fieldName)
/*
The above is a "flattened" shortcut for:
Key("field") {
Key("name", containing: \._fieldName)
}
*/
}
@Value var name: String?
@Value var fieldName: String?
}
enum TypeName: String, Decodable {
case example = "Example type"
}
@Value var title: String
@Value var nodes: [Node]
@Value var type: TypeName
}
dump(try JSONDecoder().decode(GithubGraphqlResponse.self, from: jsonData))
添加到你的 Package.Swift
文件中
...
dependencies: [
...
.package(url: "https://github.com/MPLew-is/deep-codable", branch: "main"),
],
targets: [
...
.target(
...
dependencies: [
...
.product(name: "DeepCodable", package: "deep-codable"),
]
),
...
]
]
通过定义一个编码树,表示哪些节点绑定到哪些值,使你想要解码的类型符合 DeepDecodable
协议
struct DeeplyNestedResponse: DeepDecodable {
static let codingTree = CodingTree {
Key("topLevel") {
Key("secondLevel") {
Key("thirdLevel", containing: \._property)
}
}
}
/*
Also valid is the flattened form:
static let codingTree = CodingTree {
Key("topLevel", "secondLevel", "thirdLevel", containing: \._property)
}
*/
@Value var property: String
}
/*
Corresponding JSON would look like:
{
"topLevel": {
"secondLevel": {
"thirdLevel: "{some value}"
}
}
}
*/
你的 codingTree
中的节点由以下方式初始化的 Key
组成
Key("name") { /* 更多 Keys */ }
: 不直接捕获值,但包含其他节点的节点
{"name": { ... } }
这样的序列化表示Key("name", containing: \._value)
: 应该被解码到 value
属性中的节点
所有要解码的值必须用 @Value
属性包装器包装,并且 \._{name}
语法直接引用包装实例 (不带下划线的 \.{name}
引用实际的基础值)。
将值解码到你的类型的实例中
let instance = try JSONDecoder().decode(Response.self, from: jsonData)
DeepCodable
构建在普通的 Codable
之上,因此任何解码器(例如Foundation
中的属性列表解码器 或 优秀的第三方 YAML 解码器 Yams)都可以用于解码值。
虽然解码可能是这种类型嵌套解码最常见的用例,但该包也支持使用相同的模式将扁平的 Swift 结构编码为深度嵌套的结构
struct DeeplyNestedRequest: DeepEncodable {
static let codingTree = CodingTree {
Key("topLevel") {
Key("secondLevel") {
Key("thirdLevel", containing: \.bareProperty)
}
Key("otherSecondLevel", containing: \._wrappedProperty)
}
}
/*
Also valid is the flattened form:
static let codingTree = CodingTree {
Key("topLevel") {
Key("secondLevel", "thirdLevel", containing: \.bareProperty)
Key("otherSecondLevel", containing: \._wrappedProperty)
}
}
*/
let bareProperty: String
@Value var wrappedProperty: String
}
/*
Corresponding JSON would look like:
{
"topLevel": {
"secondLevel": {
"thirdLevel: "{bareProperty}"
},
"otherSecondLevel": "{wrappedProperty}"
}
}
*/
let instance: DeeplyNestedRequest = ...
let jsonData = try JSONEncoder().encode(instance)
对于编码,您不必使用 @Value
包装器,但如果您希望支持同一类型的解码和编码,则可以使用(在这种情况下,您可以遵循 DeepCodable
作为这两个的别名)。
将 Swift 对象编码/解码为任意复杂的深度嵌套序列化表示,而无需手动编写 Codable
实现
保留编码/解码值的现有 Codable
行为,包括自定义类型
DeepCodable
只是 Codable
要求的自定义实现,这也意味着您可以嵌套 DeepCodable
对象,例如在 GithubGraphqlResponse
示例中在遵循 DeepEncodable
或 DeepDecodable
时,不要干扰相反的普通 Codable
实现(分别为 Decodable
/Encodable
)
struct Response: DeepDecodable, Encodable { ... }
的内容,并从深度嵌套的树中解码,然后像普通的 Encodable
对象一样重新编码回扁平结构对于仅符合 DeepEncodable
的类型,不需要 @Value
属性包装器
当叶子节点上的所有值都为 nil
时,省略相应的树部分
nil
值的对象不会导致类似 {"top": {"second": {"third": null} } }
的结果使用可变参数简化长路径且没有分支的情况
Key("topLevel", "secondLevel", containing: \._property)
而不是 Key("topLevel") { Key("secondLevel", containing: \._property) }