AnyCodable
是一个 Swift 包,它提供了处理异构或松散结构数据的工具,同时保持强大的类型安全并利用 Swift 强大的 Codable
协议。 它包括对动态编码键、解码嵌套数据以及无缝处理任何可编码值的支持。
AnyCodableKey
:一种灵活的编码键类型,支持字符串和整数键。AnyCodableValue
:一种通用的类型,可以解码和编码各种原始和复合值,例如数字、字符串、数组和字典。InstancesOf
:一个实用程序结构,用于从复杂数据源提取特定类型的集合。KeyedDecodingContainer
和 UnkeyedDecodingContainer
的扩展,用于简化解码特定类型的集合,包括嵌套结构。我发现在实践中,第三方 API 可能是易变的。 当您使用私有 API 时尤其如此,这些 API 会根据使用它们的人的突发奇想而发生变化。
使用此库提供的工具可以避免为那些您不一定想实际解析或使用的代码提供刚性结构,并直接获取有用的信息,同时降低 API 崩溃的可能性。
在你的 Package.swift
Swift Package Manager 清单中,将以下依赖项添加到你的 dependencies
参数中
.package(url: "https://github.com/jellybeansoup/swift-any-codable.git", from: "1.0.0"),
将依赖项添加到你在清单中声明的任何目标
.target(
name: "MyTarget",
dependencies: [
.product(name: "AnyCodable", package: "swift-any-codable"),
]
),
AnyCodableValue
可以无缝地编码和解码各种原始类型。 允许将其用作占位符,当您不确定要获取哪种类型的数据时。
import AnyCodable
enum DecodingKeys: String, Hashable {
case key, nested
}
let jsonData = Data(#"{ "key": 123, "nested": [1, 2, 3] }"#.utf8)
let decoded = try JSONDecoder().decode([DecodingKeys: AnyCodableValue].self, from: jsonData)
if let intValue = decoded[.key]?.integerValue {
print("Decoded integer value: \(intValue)")
}
if let array = decoded[.nested]?.arrayValue {
print("Decoded array: \(array)")
}
AnyCodableValue
与 AnyCodableKey
的结合为处理动态或未知结构提供了灵活的解决方案,同时支持基于字符串和整数的键。 您可以以一种通过代码仍然可以访问的方式解码不熟悉的数据,同时确保可以轻松地再次对其进行编码。
import AnyCodable
struct Post: Codable {
var title: String
var author: String
var unsupportedValues: [String: AnyCodableValue]
enum CodingKeys: String, CodingKey, CaseIterable {
case title
case author
}
init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.title = try container.decode(String.self, forKey: .title)
self.author = try container.decode(String.self, forKey: .author)
let unsupportedContainer = try decoder.container(keyedBy: AnyCodableKey.self)
var unsupportedValues: [String: AnyCodableValue] = [:]
for key in unsupportedContainer.allKeys where CodingKeys.allCases.map(AnyCodableKey.init).contains(key) == false {
unsupportedValues[key.stringValue] = try unsupportedContainer.decode(AnyCodableValue.self, forKey: key)
}
self.unsupportedValues = unsupportedValues
}
func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: AnyCodableKey.self)
try container.encode(self.title, forKey: AnyCodableKey(CodingKeys.title.rawValue))
try container.encode(self.author, forKey: AnyCodableKey(CodingKeys.author.rawValue))
for (key, value) in self.unsupportedValues {
try container.encode(value, forKey: AnyCodableKey(key))
}
}
}
let jsonData = Data(#"{"title": "Example", "author": "Jelly", "date": "2025-01-01T12:34:56Z", "draft": true}"#.utf8)
let post = try JSONDecoder().decode(Post.self, from: jsonData)
print(post) // Post(title: "Example", author: "Jelly", unsupportedValues: ["draft": .bool(true), "date": .string("2025-01-01T12:34:56Z")])
let encoded = try JSONEncoder().encode(post)
print(String(data: encoded, encoding: .utf8)!) // {"author":"Jelly","draft":true,"title":"Example","date":"2025-01-01T12:34:56Z"}
InstancesOf
简化了从复杂数据中提取特定类型的过程,即使该数据嵌套在结构深处。 这极大地简化了处理 API 的过程,在这些 API 中,您只关心结构中的特定对象,或者结构本身可能会发生变化。
以这个来自非常真实的 API 的非常真实的 JSON 响应为例
{
"data": {
"repository": {
"milestone": {
"title": "v2025.1",
"issues": {
"nodes": [
{
"number": 100,
"title": "A very real problem!"
},
{
"number": 101,
"title": "Less of a problem, more of a request."
},
]
}
}
}
}
}
无需编写完整的结构化模型集来捕获整个响应(六个!),您可以编写两个,然后使用 InstancesOf
跳过无意义的部分,直接进入您实际关心的模型
import AnyCodable
struct Milestone: Decodable, Equatable {
var title: String
var issues: [Issue]
enum CodingKeys: String, CodingKey {
case title
case issues
}
init(title: String, issues: [Issue]) {
self.title = title
self.issues = issues
}
init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
title = try container.decode(String.self, forKey: .title)
issues = try container.decode(instancesOf: Issue.self, forKey: .issues)
}
}
struct Issue: Decodable, Equatable {
var number: Int
var title: String
}
let milestones = try JSONDecoder().decode(InstancesOf<Milestone>.self, from: jsonData)
print(Array(milestones)) // [Milestone(title: "v2025.1", issues: [Issue(number: 100, title: "A very real problem!"), Issue(number: 101, title: "Less of a problem, more of a request.")])]
该项目采用 BSD 2-Clause "Simplified" 许可协议。 有关详细信息,请参阅 LICENSE 文件。