DeepCodable: 将深度嵌套的数据编码和解码为扁平的 Swift 对象

你是否曾经从 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 组成

所有要解码的值必须用 @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 作为这两个的别名)。

主要特点