Ios 如何从Swift 4中的解码器容器中获取非编码属性?

Ios 如何从Swift 4中的解码器容器中获取非编码属性?,ios,json,swift,swift4,decodable,Ios,Json,Swift,Swift4,Decodable,我使用Decodable协议来解析从外部源接收的JSON。在解码了我所知道的属性之后,JSON中仍然可能有一些未知的属性尚未解码。例如,如果外部源在将来的某个时间点向JSON添加了一个新属性,我希望通过将这些未知属性存储在[String:Any]字典(或其他方法)中来保留这些属性,这样这些值就不会被忽略 问题是,在解码我所知道的属性之后,容器上没有任何访问器来检索尚未解码的属性。我知道可以使用解码器.unkeydcontainer()来迭代每个值,但是在我的例子中,这不起作用,因为为了使它起作用

我使用
Decodable
协议来解析从外部源接收的JSON。在解码了我所知道的属性之后,JSON中仍然可能有一些未知的属性尚未解码。例如,如果外部源在将来的某个时间点向JSON添加了一个新属性,我希望通过将这些未知属性存储在
[String:Any]
字典(或其他方法)中来保留这些属性,这样这些值就不会被忽略

问题是,在解码我所知道的属性之后,容器上没有任何访问器来检索尚未解码的属性。我知道可以使用
解码器.unkeydcontainer()
来迭代每个值,但是在我的例子中,这不起作用,因为为了使它起作用,您需要知道迭代的值类型,但是JSON中的值类型并不总是相同的

下面是一个我在操场上努力实现的例子:

// 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的任何其他可能的值类型

我之所以想这样做,是为了能够持久化JSON中存在的未知属性,以便在将来的代码更新中,一旦属性键已知,我就能够适当地处理它们


我在StackOverflow和Google上做了很多搜索,但还没有遇到这个独特的案例。提前谢谢

你们不断想出新的方法来强调Swift 4编码API…;)

支持所有值类型的通用解决方案可能是不可能的。但是,对于基本类型,您可以尝试以下方法:

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

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

    init?(intValue: Int) { return nil }
    var intValue: Int? { return nil }
}
然后使用上面的
UnknownCodingKey
键控的标准
KeyedDecodingContainer
编写一个通用解码函数:

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
}
一个简单的测试:

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
[“年龄”:21,“代币”:“ABC”,“费率”:1.234]

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)