动态 JSON 解码 Swift 4

Posted

技术标签:

【中文标题】动态 JSON 解码 Swift 4【英文标题】:Dynamic JSON Decoding Swift 4 【发布时间】:2017-12-02 01:20:43 【问题描述】:

我正在尝试在 Swift 4 中解码以下 JSON:


    "token":"RdJY3RuB4BuFdq8pL36w",
    "permission":"accounts, users",
    "timout_in":600,
    "issuer": "Some Corp",
    "display_name":"John Doe",
    "device_id":"uuid824fd3c3-0f69-4ee1-979a-e8ab25558421"

问题是,JSON 中的最后 2 个元素(display_namedevice_id)可能存在也可能不存在,或者元素可能被命名为完全不同但仍然未知的名称,即 "fred": "worker", "hours" : 8

所以我想要实现的是解码已知的内容,即tokenpermissiontimeout_inissuer 以及任何其他元素(display_namedevice_id 等)将它们放入一本字典。

我的结构是这样的:

struct AccessInfo : Decodable

    let token: String
    let permission: [String]
    let timeout: Int
    let issuer: String
    let additionalData: [String: Any]

    private enum CodingKeys: String, CodingKey
    
        case token
        case permission
        case timeout = "timeout_in"
        case issuer
    

    public init(from decoder: Decoder) throws
    
        let container = try decoder.container(keyedBy: CodingKeys.self)
        token = container.decode(String.self, forKey: .token)
        permission = try container.decodeIfPresent(String.self, forKey: .permission).components(separatedBy: ",")
        timeout = try container.decode(Int.self, forKey: . timeout)
        issuer = container.decode(String.self, forKey: .issuer)

        // This is where I'm stuck, how do I add the remaining
        // unknown JSON elements into additionalData?
    


// Calling code, breviated for clarity
let decoder = JSONDecoder()
let accessInfo = try decoder.decode(AccessInfo.self, from: data!)

如果有人可以提供一些指导,那么能够解码 JSON 也可能包含动态信息的已知结构的一部分就是我所处的位置。

谢谢

【问题讨论】:

您的 JSON 无效。请提供真实 JSON。 重复***.com/questions/45598461/… 【参考方案1】:

受@matt cmets 启发,这是我使用的完整示例。我扩展了KeyedDecodingContainer 来解码未知密钥并提供一个参数来过滤掉已知的CodingKeys

JSON 示例


    "token":"RdJY3RuB4BuFdq8pL36w",
    "permission":"accounts, users",
    "timout_in":600,
    "issuer": "Some Corp",
    "display_name":"John Doe",
    "device_id":"uuid824fd3c3-0f69-4ee1-979a-e8ab25558421"

Swift 结构

struct AccessInfo : Decodable

    let token: String
    let permission: [String]
    let timeout: Int
    let issuer: String
    let additionalData: [String: Any]

    private enum CodingKeys: String, CodingKey
    
        case token
        case permission
        case timeout = "timeout_in"
        case issuer
    

    public init(from decoder: Decoder) throws
    
        let container = try decoder.container(keyedBy: CodingKeys.self)
        token = container.decode(String.self, forKey: .token)
        permission = try container.decode(String.self, forKey: .permission).components(separatedBy: ",")
        timeout = try container.decode(Int.self, forKey: . timeout)
        issuer = container.decode(String.self, forKey: .issuer)

        // Additional data decoding
        let container2 = try decoder.container(keyedBy: AdditionalDataCodingKeys.self)
        self.additionalData = container2. decodeUnknownKeyValues(exclude: CodingKeys.self)
    


private struct AdditionalDataCodingKeys: CodingKey

    var stringValue: String
    init?(stringValue: String)
    
        self.stringValue = stringValue
    

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

KeyedDecodingContainer 扩展

extension KeyedDecodingContainer where Key == AdditionalDataCodingKeys

    func decodeUnknownKeyValues<T: CodingKey>(exclude keyedBy: T.Type) -> [String: Any]
    
        var data = [String: Any]()

        for key in allKeys
        
            if keyedBy.init(stringValue: key.stringValue) == nil
            
                if let value = try? decode(String.self, forKey: key)
                
                    data[key.stringValue] = value
                
                else if let value = try? decode(Bool.self, forKey: key)
                
                    data[key.stringValue] = value
                
                else if let value = try? decode(Int.self, forKey: key)
                
                    data[key.stringValue] = value
                
                else if let value = try? decode(Double.self, forKey: key)
                
                    data[key.stringValue] = value
                
                else if let value = try? decode(Float.self, forKey: key)
                
                    data[key.stringValue] = value
                
                else
                
                    NSLog("Key %@ type not supported", key.stringValue)
                
            
        

        return data
    

调用代码

let decoder = JSONDecoder()
let accessInfo = try decoder.decode(AccessInfo.self, from: data!)

print("Token: \(accessInfo.token)")
print("Permission: \(accessInfo.permission)")
print("Timeout: \(accessInfo.timeout)")
print("Issuer: \(accessInfo.issuer)")
print("Additional Data: \(accessInfo.additionalData)")

输出

Token: RdJY3RuB4BuFdq8pL36w
Permission: ["accounts", "users"]
Timeout: 600
Issuer: "Some Corp"
Additional Data: ["display_name":"John Doe", "device_id":"uuid824fd3c3-0f69-4ee1-979a-e8ab25558421"]

【讨论】:

经过数小时的搜索,终于帮到了我!我不明白的一件事是以下行:if keyedBy.init(stringValue: key.stringValue) == nil。为什么返回 nil?【参考方案2】:

这个问题实际上是Swift 4 Decodable with keys not known until decoding time 的重复。一旦您了解了构建一个最低限度的 CodingKey 采用者结构作为您的编码密钥的技巧,您就可以将它用于任何字典。

在这种情况下,您将使用键控容器的 allKeys 来获取未知的 JSON 字典键。

为了演示,我将把自己限制在 JSON 字典中完全未知的部分。想象一下这个 JSON:

let j = """

    "display_name":"John Doe",
    "device_id":"uuid824fd3c3-0f69-4ee1-979a-e8ab25558421"

"""
let jdata = j.data(using: .utf8)!

假设我们不知道那个字典里有什么,除了它有字符串键和字符串值的事实。所以我们想解析jdata,而不知道它的键是什么。

因此,我们有一个由一个字典属性组成的结构:

struct S 
    let stuff : [String:String]

现在的问题是如何将该 JSON 解析为该结构 - 即如何使该结构符合 Decodable 并处理该 JSON。

方法如下:

struct S : Decodable 
    let stuff : [String:String]
    private struct CK : CodingKey 
        var stringValue: String
        init?(stringValue: String) 
            self.stringValue = stringValue
        
        var intValue: Int?
        init?(intValue: Int) 
            return nil
        
    
    init(from decoder: Decoder) throws 
        let con = try! decoder.container(keyedBy: CK.self)
        var d = [String:String]()
        for key in con.allKeys 
            let value = try! con.decode(String.self, forKey:key)
            d[key.stringValue] = value
        
        self.stuff = d
    

现在我们解析:

let s = try! JSONDecoder().decode(S.self, from: jdata)

我们得到一个 S 实例,其stuff 就是这个字典:

["device_id": "uuid824fd3c3-0f69-4ee1-979a-e8ab25558421", "display_name": "John Doe"]

这正是我们想要的结果。

【讨论】:

如果未知数据并不总是[String:String]类型怎么办? 这是一个不同的问题,在 Stack Overflow 上也已经彻底解决了这个问题。 感谢您的回复。我查看了swift-4-decodable-with-keys-not-known-until-decoding-time 和@matt 的代码示例工作正常。当实现变得棘手时,将 known keysunknown keys 组合在一个解析/解码操作中时,因为您想对容器进行两次解码......或者解码为通用方法,并手动分配已知键,将剩余的未知键放入字典[String: String] 你进去了两次,一次是用已知的钥匙,就像你已经展示的那样,再一次潜入未知的钥匙,就像我做的那样。我需要把整件事都拼出来吗? 有人可以提供关于已知键但未知数据类型的 SO 问题的链接吗?

以上是关于动态 JSON 解码 Swift 4的主要内容,如果未能解决你的问题,请参考以下文章

在 Swift 中动态解码任意 json 字段

如何在 Swift 中使用动态键(在根级别)解码此 JSON?

Swift - 将类型动态传递给 JSONDecoder

如何使用 jsonDecoder 处理来自 JSON 响应的动态键?

处理动态 JSON 模式解码

IOS - 如何在 Swift 3 中从 JSON 中捕获动态数组维度