是否可以使用JSONDecoder解码其他参数? [英] Is it possible to decode additional parameters using JSONDecoder?

查看:144
本文介绍了是否可以使用JSONDecoder解码其他参数?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

后端有一些回复:

{
    "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属性中?

How to make JSONDecoder combine all unknown key-value pairs into params property?

推荐答案

Decodable非常强大.它可以解码完全任意的JSON,因此这只是该问题的一个子集.有关完全解决的JSON Decodable,请参见此 JSON .

Decodable is incredibly powerful. It can decode completely arbitrary JSON, so this is just a sub-set of that problem. For a fully worked-out JSON Decodable, see this JSON.

我将从示例中提取Key的概念,但为简单起见,我将假定值必须为IntString.您可以将parameters设置为[String: JSON],并改用我的JSON解码器.

I'll pull the concept of Key from example, but for simplicity I'll assume that values must be either Int or String. You could make parameters be [String: JSON] and use my JSON decoder instead.

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])


其他想法


ADDITIONAL THOUGHTS

我认为以下评论非常重要,未来的读者应该仔细阅读.我想展示需要很少的代码重复,并且可以轻松地提取和重用其中的多少代码,这样就不需要魔术或动态功能.

I think the comments below are really important and future readers should look them over. I wanted to show how little code duplication is required, and how much of this can be easily extracted and reused, such that no magic or dynamic features are required.

首先,提取常见且可重复使用的片段:

First, extract the pieces that are common and reusable:

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(基本上,这是您必须为).作为交换,我们得到了完整的编译时类型检查,因此我们知道当我们遇到意外情况时它不会崩溃.

It would be nice if there were some magic way to say "please take care of these properties in the default way," but I don't quite know what that would look like frankly. The amount of code here is about the same as for implementing NSCoding, and much less than for implementing against NSJSONSerialization, and is easily handed to swiftgen if it were too tedious (it's basically the code you have to write for init). In exchange, we get full compile-time type checking, so we know it won't crash when we get something unexpected.

有几种方法可以使上述内容更短一些(我目前正在考虑有关KeyPath的想法,以使其更加方便).关键是当前的工具非常强大,值得探索.

There are a few ways to make even the above a bit shorter (and I'm currently thinking about ideas involving KeyPaths to make it even more convenient). The point is that the current tools are very powerful and worth exploring.

这篇关于是否可以使用JSONDecoder解码其他参数?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆