Swift Codable:如何“传递”未分析的 json 子对象

Tik*_*itu 3 swift codable

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

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

(很可能会有一个关于编码相同内容的后续问题,所以如果您碰巧有洞察力,请随时提及。)

Muk*_*esh 6

您可以创建一个自定义字典Decodable,该字典对值进行解码并将其存储在 an 中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
    }
}
Run Code Online (Sandbox Code Playgroud)

现在您可以使用此结构来解码字典,如下所示:

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
    }
}
Run Code Online (Sandbox Code Playgroud)

我们来测试一下:

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)
Run Code Online (Sandbox Code Playgroud)

这是输出:

name
["bool": true, "string": "rt", "int": 1, "float": 1.12, "dict": ["test": "String"]]
Run Code Online (Sandbox Code Playgroud)