是否可以使用 JSONDecoder 解码附加参数?

2024-01-04

我们收到后端返回的一些响应:

{
    "name": "Some name",
    "number": 42,
    ............
    "param0": value0,
    "param1": value1,
    "param2": value2
}

响应模型结构:

struct Model: Codable {
    let name: String
    let number: Int
    let params: [String: Any]
}

怎么做JSONDecoder将所有未知的键值对组合成params财产?


Decodable非常强大。它可以解码完全任意的 JSON,所以这只是该问题的一个子集。对于完整的 JSONDecodable,看这个JSON https://gist.github.com/rnapier/d91175be2f737aca7ad476759948666e.

我将拉出这个概念Key从示例中,但为了简单起见,我假设值必须是Int or String。你可以做parameters be [String: JSON]并使用我的 JSON 解码器。

struct Model: Decodable {
    let name: String
    let number: Int
    let params: [String: Any]

    // An arbitrary-string Key, with a few "well known and required" keys
    struct Key: CodingKey, Equatable {
        static let name = Key("name")
        static let number = Key("number")

        static let knownKeys = [Key.name, .number]

        static func ==(lhs: Key, rhs: Key) -> Bool {
            return lhs.stringValue == rhs.stringValue
        }

        let stringValue: String
        init(_ string: String) { self.stringValue = string }
        init?(stringValue: String) { self.init(stringValue) }
        var intValue: Int? { return nil }
        init?(intValue: Int) { return nil }
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: Key.self)

        // First decode what we know
        name = try container.decode(String.self, forKey: .name)
        number = try container.decode(Int.self, forKey:. number)

        // Find all the "other" keys
        let optionalKeys = container.allKeys
            .filter { !Key.knownKeys.contains($0) }

        // Walk through the keys and try to decode them in every legal way
        // Throw an error if none of the decodes work. For this simple example
        // I'm assuming it is a String or Int, but this is also solvable for
        // arbitarily complex data (it's just more complicated)
        // This code is uglier than it should be because of the `Any` result.
        // It could be a lot nicer if parameters were a more restricted type
        var p: [String: Any] = [:]
        for key in optionalKeys {
            if let stringValue = try? container.decode(String.self, forKey: key) {
                p[key.stringValue] = stringValue
            } else {
                 p[key.stringValue] = try container.decode(Int.self, forKey: key)
            }
        }
        params = p
    }
}

let json = Data("""
{
    "name": "Some name",
    "number": 42,
    "param0": 1,
    "param1": "2",
    "param2": 3
}
""".utf8)

try JSONDecoder().decode(Model.self, from: json)
// Model(name: "Some name", number: 42, params: ["param0": 1, "param1": "2", "param2": 3])

其他想法

我认为下面的评论非常重要,未来的读者应该仔细阅读。我想展示只需要很少的代码重复,以及其中有多少可以轻松提取和重用,这样就不需要任何魔法或动态功能。

首先,提取常见且可重用的部分:

func additionalParameters<Key>(from container: KeyedDecodingContainer<Key>,
                               excludingKeys: [Key]) throws -> [String: Any]
    where Key: CodingKey {
        // Find all the "other" keys and convert them to Keys
        let excludingKeyStrings = excludingKeys.map { $0.stringValue }

        let optionalKeys = container.allKeys
            .filter { !excludingKeyStrings.contains($0.stringValue)}

        var p: [String: Any] = [:]
        for key in optionalKeys {
            if let stringValue = try? container.decode(String.self, forKey: key) {
                p[key.stringValue] = stringValue
            } else {
                p[key.stringValue] = try container.decode(Int.self, forKey: key)
            }
        }
        return p
}

struct StringKey: CodingKey {
    let stringValue: String
    init(_ string: String) { self.stringValue = string }
    init?(stringValue: String) { self.init(stringValue) }
    var intValue: Int? { return nil }
    init?(intValue: Int) { return nil }
}

现在,解码器Model减少到这个

struct Model: Decodable {
    let name: String
    let number: Int
    let params: [String: Any]

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: StringKey.self)

        name = try container.decode(String.self, forKey: StringKey("name"))
        number = try container.decode(Int.self, forKey: StringKey("number"))
        params = try additionalParameters(from: container,
                                          excludingKeys: ["name", "number"].map(StringKey.init))
    }
}

如果有某种神奇的方式来表达“请以默认方式处理这些属性”,那就太好了,但坦白说,我不太知道那会是什么样子。这里的代码量与实现的代码量大致相同NSCoding,并且比针对实施要少得多NSJSONSerialization,如果它太乏味的话,很容易交给 swiftgen (这基本上是你必须编写的代码init)。作为交换,我们获得了完整的编译时类型检查,因此我们知道当我们遇到意外情况时它不会崩溃。

有几种方法可以使上述内容变得更短(我目前正在考虑涉及 KeyPaths 的想法,以使其更加方便)。重点是,目前的工具非常强大,值得探索。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

是否可以使用 JSONDecoder 解码附加参数? 的相关文章

随机推荐