如何从Swift 4的Decoder容器中获取未解码的属性? [英] How to get the nondecoded attributes from a Decoder container in Swift 4?

查看:135
本文介绍了如何从Swift 4的Decoder容器中获取未解码的属性?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用Decodable协议来解析从外部来源收到的JSON.解码了我确实知道的属性后,JSON中仍然可能存在一些未知且尚未解码的属性.例如,如果外部源在将来的某个时间点向JSON添加了新属性,我想通过将它们存储在[String: Any]词典(或替代方法)中来保留这些未知属性,以使这些值不会被忽略

I'm using the Decodable protocol in order to parse JSON received from an external source. After decoding the attributes that I do know about there still may be some attributes in the JSON that are unknown and have not yet been decoded. For example, if the external source added a new attribute to the JSON at some future point in time I would like to hold onto these unknown attributes by storing them in a [String: Any] dictionary (or an alternative) so the values do not get ignored.

问题是,在解码了我确实知道的属性后,容器上没有任何访问器可以检索尚未解码的属性.我知道decoder.unkeyedContainer()可以用来遍历每个值,但是在我的情况下这是行不通的,因为要使其正常工作,您需要知道要遍历哪种值类型,但是要遍历其中的值类型JSON并不总是相同的.

The issue is that after decoding the attributes that I do know about there isn't any accessors on the container to retrieve the attributes that have not yet been decoded. I'm aware of the decoder.unkeyedContainer() which I could use to iterate over each value however this would not work in my case because in order for that to work you need to know what value type you're iterating over but the value types in the JSON are not always identical.

这是操场上我想要达到的目标的一个示例:

Here is an example in playground for what I'm trying to achieve:

// Playground
import Foundation

let jsonData = """
{
    "name": "Foo",
    "age": 21
}
""".data(using: .utf8)!

struct Person: Decodable {
    enum CodingKeys: CodingKey {
        case name
    }

    let name: String
    let unknownAttributes: [String: Any]

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.name = try container.decode(String.self, forKey: .name)

        // I would like to store the `age` attribute in this dictionary
        // but it would not be known at the time this code was written.
        self.unknownAttributes = [:]
    }
}

let decoder = JSONDecoder()
let person = try! decoder.decode(Person.self, from: jsonData)

// The `person.unknownAttributes` dictionary should
// contain the "age" attribute with a value of 21.

我希望unknownAttributes字典在这种情况下存储age属性和值,以及将来可能从外部源添加到JSON的任何其他可能的值类型.

I would like for the unknownAttributes dictionary to store the age attribute and value in this case and any other possible value types if they get added to the JSON from the external source in the future.

我想要执行类似操作的原因是,我可以保留JSON中存在的未知属性,以便在以后的代码更新中,一旦知道属性键,我将能够适当地处理它们.

The reason I am wanting to do something like this is so that I can persist the unknown attributes present in the JSON so that in a future update of the code I will be able to handle them appropriately once the attribute keys are known.

我已经在StackOverflow和Google上进行了大量搜索,但是还没有遇到这种独特的情况.预先感谢!

I've done plenty of searching on StackOverflow and Google but haven't yet encountered this unique case. Thanks in advance!

推荐答案

你们不断提出新颖的方法来强调Swift 4编码API ...;)

You guys keep coming up with novel ways to stress the Swift 4 coding APIs... ;)

可能无法使用支持 all 值类型的通用解决方案.但是,对于原始类型,您可以尝试以下方法:

A general solution, supporting all value types, might not be possible. But, for primitive types, you can try this:

使用基于字符串的键创建简单的CodingKey类型:

Create a simple CodingKey type with string-based keys:

struct UnknownCodingKey: CodingKey {
    init?(stringValue: String) { self.stringValue = stringValue }
    let stringValue: String

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

然后使用以上UnknownCodingKey键控的标准KeyedDecodingContainer编写常规解码功能:

Then write a general decoding function using the standard KeyedDecodingContainer keyed by the UnknownCodingKey above:

func decodeUnknownKeys(from decoder: Decoder, with knownKeys: Set<String>) throws -> [String: Any] {
    let container = try decoder.container(keyedBy: UnknownCodingKey.self)
    var unknownKeyValues = [String: Any]()

    for key in container.allKeys {
        guard !knownKeys.contains(key.stringValue) else { continue }

        func decodeUnknownValue<T: Decodable>(_ type: T.Type) -> Bool {
            guard let value = try? container.decode(type, forKey: key) else {
                return false
            }
            unknownKeyValues[key.stringValue] = value
            return true
        }
        if decodeUnknownValue(String.self) { continue }
        if decodeUnknownValue(Int.self)    { continue }
        if decodeUnknownValue(Double.self) { continue }
        // ...
    }
    return unknownKeyValues
}

最后,使用decodeUnknownKeys函数填充unknownAttributes词典:

Finally, use the decodeUnknownKeys function to fill your unknownAttributes dictionary:

struct Person: Decodable {
    enum CodingKeys: CodingKey {
        case name
    }

    let name: String
    let unknownAttributes: [String: Any]

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.name = try container.decode(String.self, forKey: .name)

        let knownKeys = Set(container.allKeys.map { $0.stringValue })
        self.unknownAttributes = try decodeUnknownKeys(from: decoder, with: knownKeys)
    }
}

一个简单的测试:

let jsonData = """
{
    "name": "Foo",
    "age": 21,
    "token": "ABC",
    "rate": 1.234
}
""".data(using: .utf8)!

let decoder = JSONDecoder()
let person = try! decoder.decode(Person.self, from: jsonData)
print(person.name)
print(person.unknownAttributes)

打印:

Foo
["age":21,"token":"ABC","rate":1.234]

Foo
["age": 21, "token": "ABC", "rate": 1.234]

这篇关于如何从Swift 4的Decoder容器中获取未解码的属性?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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