Dynamic JSON Decoding Swift 4

前端 未结 2 1597
半阙折子戏
半阙折子戏 2020-12-09 07:02

I\'m trying to decode the following JSON in Swift 4:

{
    \"token\":\"RdJY3RuB4BuFdq8pL36w\",
    \"permission\":\"accounts, users\",
    \"timout_in\":600,         


        
相关标签:
2条回答
  • 2020-12-09 07:35

    The question is actually a duplicate of Swift 4 Decodable with keys not known until decoding time. Once you understand the trick about building a rock-bottom minimal CodingKey adopter struct as your coding key, you can use it for any dictionary.

    In this instance, you would use the keyed container's allKeys to get the unknown JSON dictionary keys.

    To demonstrate, I will confine myself to just the completely unknown part of the JSON dictionary. Imagine this JSON:

    let j = """
    {
        "display_name":"John Doe",
        "device_id":"uuid824fd3c3-0f69-4ee1-979a-e8ab25558421"
    }
    """
    let jdata = j.data(using: .utf8)!
    

    Presume that we have no idea what's in that dictionary, beyond that the fact that it has String keys and String values. So we want to parse jdata without knowing anything about what its keys are.

    We therefore have a struct consisting of one dictionary property:

    struct S {
        let stuff : [String:String]
    }
    

    The question now is how to parse that JSON into that struct - i.e., how to make that struct conform to Decodable and deal with that JSON.

    Here's how:

    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
        }
    }
    

    Now we parse:

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

    And we get an S instance whose stuff is this dictionary:

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

    And that is the very result we wanted.

    0 讨论(0)
  • 2020-12-09 07:52

    Inspired by @matt comments, here is the full sample I've gone with. I extended the KeyedDecodingContainer to decode the unknown keys and provide a parameter to filter out known CodingKeys.

    Sample JSON

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

    Swift structs

    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

    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
        }
    }
    

    Calling code

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

    Output

    Token: RdJY3RuB4BuFdq8pL36w
    Permission: ["accounts", "users"]
    Timeout: 600
    Issuer: "Some Corp"
    Additional Data: ["display_name":"John Doe", "device_id":"uuid824fd3c3-0f69-4ee1-979a-e8ab25558421"]
    
    0 讨论(0)
提交回复
热议问题