Swift Codable:如何 "pass through" 一个 json 未分析的子对象

Swift Codable: how to "pass through" a json sub-object unanalysed

由于复杂的原因,我发现自己在违背 Codable 的原则:在解码 json 对象时,我想保留键 extra [=30 下的子对象=],存储为 [String: Any] 字典,但 [String: Any](当然)不是 Decodable。有什么办法吗?

第一个回答当然是"don't"。我的回答是"I need to":我需要让两个Codable传递数据,其中第一个解码异构对象列表(每个对象都有一个键 name),而第二遍使用以这些 name 值为键的字典,并且是正确的类型安全的。第一遍不能是类型安全的,因为它在异构列表上运行,但它需要保留第二遍将使用的所有数据。值得庆幸的是,所有异构数据都隐藏在那个 extra 键下,但我仍然不知道该怎么做。

(很可能会有关于 _en_coding 相同问题的后续问题,所以如果您碰巧有见识,请随时提出。)

我得到的最接近的是以下 Playgrounds 代码:

struct Foo: Codable {
    let bar: [String: Any]

    enum CodingKeys: String, CodingKey {
        case bar
    }

    init(bar: [String: Any]) {
        self.bar = bar
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        let barString = try values.decode(String.self, forKey: .bar)
        let barData = Data(barString.utf8)
        let json: [String: Any] = try JSONSerialization.jsonObject(with: barData, options: .allowFragments) as! [String: Any]
        bar = json
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        let barData = try JSONSerialization.data(withJSONObject: bar, options: .sortedKeys)
        let barString = String(decoding: barData, as: UTF8.self)
        try container.encode(barString, forKey: .bar)
    }
}

let foo = Foo(bar: ["someInt": 1])
let fooData = try! JSONEncoder().encode(foo)
print(String(decoding: fooData, as: UTF8.self))
print(String(decoding: fooData, as: UTF8.self).replacingOccurrences(of: "\\"", with: "\""))

let decodedFoo = try! JSONDecoder().decode(Foo.self, from: fooData)
print(decodedFoo)

以上打印:

{"bar":"{\"someInt\":1}"}
{"bar":"{"someInt":1}"}
Foo(bar: ["someInt": 1])

它还不完美,因为它在生成的 JSON 字符串中转义了 ",因此可能会破坏其中的某些内容。额外的字符串替换过程可能不是一件令人愉快的事情。

您可以创建一个自定义字典 ​​Decodable,它将值解码并存储在 Dictionary 中,然后将此类型用作要存储原始字典的键。

这是解码器:

struct DictionaryDecodable: Decodable {

    let dictionary : [String: Any]

    private struct Key : CodingKey {

        var stringValue: String
        init?(stringValue: String) {
            self.stringValue = stringValue
        }

        var intValue: Int?
        init?(intValue: Int) {
            return nil
        }
    }

    init(from decoder: Decoder) throws {
        let con = try decoder.container(keyedBy: Key.self)
        var dict = [String: Any]()
        for key in con.allKeys {
            if let value = try? con.decode(String.self, forKey:key) {
                dict[key.stringValue] = value
            } else if let value = try? con.decode(Int.self, forKey:key) {
                dict[key.stringValue] = value
            } else if let value = try? con.decode(Double.self, forKey:key) {
                dict[key.stringValue] = value
            } else if let value = try? con.decode(Bool.self, forKey:key) {
                dict[key.stringValue] = value
            } else if let data = try? con.decode(DictionaryDecodable.self, forKey:key)  {
                dict[key.stringValue] = data.dictionary
            }

        }
        self.dictionary = dict
    }
}

现在你可以像这样使用这个结构来解码字典:

struct Test: Decodable {
    let name: String
    let data: [String: Any]

    enum Keys: String, CodingKey {
        case name
        case data
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: Keys.self)
        name = try container.decode(String.self, forKey: .name)
        data = try container.decode(DictionaryDecodable.self, forKey: .data).dictionary // Here use DictionaryDecodable
    }
}

让我们测试一下:

let data = """
{
    "name": "name",
    "data": {
        "string": "rt",
        "bool": true,
        "float": 1.12,
        "int": 1,
        "dict": {
            "test": "String"
        }
    }
}
"""

let s = try JSONDecoder().decode(Test.self, from: data.data(using: .utf8)!)
print(s.name)
print(s.data)

这是输出:

name
["bool": true, "string": "rt", "int": 1, "float": 1.12, "dict": ["test": "String"]]

Swift Forums post 似乎提供了解决方案。它是这样开始的:

public enum JSON : Codable {
    case null
    case number(NSNumber)
    case string(String)
    case array([JSON])
    case dictionary([String : JSON])
    // ...

想法是制作一个明确的 JSON 枚举类型,即 Codable,直接编码 json 结构(case string(String); case array([JSON]); 等)。未分析的子对象被强类型化为 JSON 并且可以通过 Codable 成为 encoded/decoded,并且(作为奖励)也可以使用 "just Swift" 进行分析(打开case and do stuff) 如有必要。