我正在寻找一种在 Encodable 中创建逃生舱口的方法。有许多 JSON API 比 Encodable 所允许的更灵活。以 Anthropic API 为例,它允许您指定 JSON 模式作为请求正文的一部分。程序员无法提前知道此模式,因为最终用户可以制作它。请参阅此处的
input_schema
字段:https://docs.anthropic.com/en/docs/build-with-claude/tool-use
我的目标是在有意义的情况下执行严格的合同(固定字段)并允许 当结构灵活时,可用于
[String: Any]
的逃生舱口。这些目标很难实现。我已经为此工作了两天,编写各种编码器黑客试图获得所需的输出。我发现,完全留在无类型世界或完全留在严格契约世界中都是微不足道的:
这非常灵活,并且开箱即用:
let myTree: [String: Any] = [
"a": "xyz",
"b": [
"c": 2,
"d": false
]
]
do {
let data = try JSONSerialization.data(withJSONObject: myTree)
print(String(decoding: data, as: UTF8.self))
} catch {
print("Could not convert flexible dictionary to JSON: \(error)")
}
这是非常严格的,并且开箱即用:
struct Root: Encodable {
let a: String
let b: Child
}
struct Child: Encodable {
let c: Int
let d: Bool
}
let myTree = Root(a: "xyz", b: Child(c: 2, d: false))
do {
let encoder = JSONEncoder()
encoder.outputFormatting = .sortedKeys
let data = try encoder.encode(myTree)
print(String(decoding: data, as: UTF8.self))
} catch {
print("Could not convert encodable struct to JSON: \(error)")
}
如果您运行这两个命令,您将看到两个 print 语句生成相同的 JSON,太棒了!现在假设字段
b
的结构事先未知,例如对于用户指定架构的 API。我想做这个:
struct RootWithEscapeHatch: Encodable {
let a: String
let b: [String: Any]
private enum Fields: CodingKey {
case a
case b
}
func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: Fields.self)
try container.encode(self.a, forKey: .a)
// try container.encode(self.b, forKey: .b) <-- What goes here?
}
}
let myFailingTree = RootWithEscapeHatch(a: "xyz", b: ["c": 2, "d": false])
do {
let data = try JSONEncoder().encode(myFailingTree)
print(String(decoding: data, as: UTF8.self))
} catch {
print("Could not convert encodable with escape hatch to JSON: \(error)")
}
您可以看到
myFailingTree
与上面的示例 myTree
同构。我希望打印语句生成相同的 JSON。如果您对“这里有什么?”中的内容有所了解线,请告诉我。我正在寻找一个通用的解决方案,即我不想硬编码结构永远是["c": 2, "d": false]
。关键是任何 [String: Any]
字段都应该是可序列化的,就像这个问题中的第一个示例一样。
谢谢!
⚠️ 我不建议在生产中这样做,但它可能会帮助你想出一个想法:)
使用
JSONSerialization
将字典编码为 json 字符串。
func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: Fields.self)
try container.encode(self.a, forKey: .a)
let data = try JSONSerialization.data(withJSONObject: b)
try container.encode(String(data: data, encoding: .utf8), forKey: .b)
}
这会导致
b
值再次被转义!所以从结果中删除转义(不是这样的):
print(String(decoding: data, as: UTF8.self).replacingOccurrences(of: "\\\"", with: "\""))
结果:
{"b":"{"c":2,"d":false}","a":"xyz"}
💡顺便说一句,只有2种情况:
因此,也许根据情况使用 switch case 可以帮助您决定根据需要使用
Encoder
或 JSONSerialization
,如以下伪代码:
func encodedData() throws -> Data {
return switch wrappedValue {
case let .knownSchema(myWellKnownObject): try JSONEncoder().encode(myWellKnownObject)
case let .unknownSchema(myUnknownObject): try JSONSerialization.data(withJSONObject: myUnknownObject)
}
我希望这能给你一些想法