Динамическое декодирование JSON Swift 4

Я пытаюсь декодировать следующий JSON в Swift 4:

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

Проблема в том, что последние 2 элемента (display_name и device_id) в JSON могут существовать или не существовать, или элементы могут быть названы как-то совершенно по-другому, но все же неизвестно, т.е. "fred": "worker", "hours" : 8

Итак, чего я пытаюсь добиться, так это декодировать то, что известно, то есть token, permission, timeout_in и issuer, а любые другие элементы (display_name, device_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 также может содержать динамическую информацию, - вот где я нахожусь, если кто-нибудь может дать какое-то руководство.

Спасибо


person user9041624    schedule 02.12.2017    source источник
comment
Ваш JSON недействителен. Предоставьте настоящий JSON.   -  person matt    schedule 02.12.2017
comment
Дубликат stackoverflow.com/questions/45598461/   -  person matt    schedule 02.12.2017


Ответы (2)


Вдохновленный комментариями @matt, вот полный образец, с которым я работал. Я расширил 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"]
person user9041624    schedule 03.12.2017
comment
После нескольких часов поиска это, наконец, помогло мне! Единственное, что я не понимаю, это следующая строка: if keyedBy.init(stringValue: key.stringValue) == nil. Почему это возвращает ноль? - person Sami; 10.05.2020

Вопрос на самом деле является дубликатом Swift 4, декодируемого с ключами неизвестно до момента декодирования. Как только вы поймете, как создать минимальную структуру адаптации 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"]

И это именно тот результат, который мы хотели.

person matt    schedule 02.12.2017
comment
А что, если неизвестные данные не всегда имеют тип [String:String]? - person Santhosh R; 02.12.2017
comment
Это другая проблема, которая также была подробно рассмотрена здесь, в Stack Overflow. - person matt; 02.12.2017
comment
Спасибо за ответы. Я просмотрел swift-4-decoded-with-keys- not-known-until-decoding-time и пример кода от @matt работают нормально. Именно при объединении известных ключей и неизвестных ключей в одной операции синтаксического анализа/декодирования реализация становится сложной, потому что вы хотите дважды декодировать контейнер... Или декодировать как общий подход и вручную назначать известные ключи, удаляя остальные неизвестные ключи в словарь [String: String] - person user9041624; 03.12.2017
comment
Вы входите дважды, один раз с известными ключами, как вы уже показали, и еще раз ныряя за неизвестными ключами, как это сделал я. Нужно ли мне все расписывать? - person matt; 03.12.2017
comment
Может ли кто-нибудь предоставить ссылку на вопрос SO об известных ключах, но неизвестном типе данных? - person Martheli; 30.12.2017