如何在 swift 的 Encodable 中创建逃生舱口

问题描述 投票:0回答:1

我正在寻找一种在 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]
字段都应该是可序列化的,就像这个问题中的第一个示例一样。

谢谢!

json swift codable encodable
1个回答
0
投票

🎮实验解决方案:

⚠️ 我不建议在生产中这样做,但它可能会帮助你想出一个想法:)

使用

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种情况:

  1. 你知道架构
  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)
}

我希望这能给你一些想法

最新问题
© www.soinside.com 2019 - 2025. All rights reserved.