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

Posted

技术标签:

【中文标题】如何从 Swift 4 中的解码器容器中获取未解码的属性?【英文标题】:How to get the nondecoded attributes from a Decoder container in Swift 4? 【发布时间】:2017-12-07 00:40:31 【问题描述】:

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

问题是在解码我知道的属性后,容器上没有任何访问器来检索尚未解码的属性。我知道 decoder.unkeyedContainer() 可以用来迭代每个值,但是这在我的情况下不起作用,因为为了使其工作,您需要知道您正在迭代的值类型,但值类型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 中存在的未知属性,以便在将来的代码更新中,一旦知道属性键,我就能够适当地处理它们。

我在 *** 和 Google 上进行了大量搜索,但还没有遇到这种独特的情况。提前致谢!

【问题讨论】:

【参考方案1】:

你们不断想出新的方法来强调 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

最后,使用decodeUnknownKeys 函数填充您的unknownAttributes 字典:

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

【讨论】:

以上是关于如何从 Swift 4 中的解码器容器中获取未解码的属性?的主要内容,如果未能解决你的问题,请参考以下文章

我如何使用解码器在swift 4中解析tableview中的json数组

如何在 swift 4 中使用 JSONDecoder 从嵌套 JSON 中获取数据?

如何在 Swift 4 中解码 JSON(JSON Web 令牌)?

通过使用 Alamofire 和解码获取 JSON - Swift 4

ffmpeg如何从url获取视频帧数据

Swift 4 Xcode 使用 JSON 解码的问题