Разобрать JSON с переменными ключами

Я пытаюсь разобрать JSON с курсами валют, содержащими динамические ключи и динамическое количество значений. Выходные данные зависят от входных параметров, таких как базовая валюта и несколько валют для сравнения.

Пример JSON:

{
"USD_AFN": 70.129997,
"USD_AUD": 1.284793,
"USD_BDT": 82.889999,
"USD_BRL": 3.418294,
"USD_KHR": 4004.99952
}

, or:

{
"EUR_CAD": 0.799997
}

Кроме того, я должен иметь возможность изменять базовую валюту и валюты для сравнения, а также изменять количество валют для сравнения. Я уже пробовал это ответ.

Каков оптимальный способ справиться с этим?

Спасибо

Дополнительная информация

Итак, я сделал структуру без инициализатора

struct CurrencyRate: Codable { 
var results : [String:Double] 
} 

и пытаюсь его расшифровать

 do { let results = try decoder.decode(CurrencyRate.self, from: dataToDecode) print(results) } catch { print("Error") }

Я все еще получаю сообщение об ошибке.

В конце концов, мне просто нужен массив курсов валют (значений), чтобы заполнить его в табличном представлении.


person Todd Page    schedule 16.04.2018    source источник
comment
Я бы сохранил словарь (расшифровать в [String:Double])   -  person vadian    schedule 16.04.2018
comment
Я пытался использовать эту структуру struct CurrencyRate: Codable { var results : [String:Double] init(results: [String:Double]) { self.results = results } и let results = try decoder.decode(CurrencyRate.self, from: dataToDecode) , но обнаружил ошибку. Возможно, моя структура не подходит для этой ситуации?   -  person Todd Page    schedule 16.04.2018
comment
Это весь JSON? Если да, то код должен работать. Вам не нужен инициализатор в структуре, даже без соответствия Codable   -  person vadian    schedule 16.04.2018
comment
Да, это весь JSON, но он может содержать любое количество объектов и любую комбинацию ключей (XXX_XXX). Итак, я создал структуру без инициализатора struct CurrencyRate: Codable { var results : [String:Double] } и пытаюсь ее декодировать do { let results = try decoder.decode(CurrencyRate.self, from: dataToDecode) print(results) } catch { print("Error") }, но все равно получаю ошибку. Что я делаю не так?   -  person Todd Page    schedule 17.04.2018
comment
Плохая идея помещать в комментарий больше, чем строку кода. Добавьте это к вопросу, если вам нравится делать это таким образом. В вашем JSON отсутствует ключ results, поэтому он не будет анализироваться. В настоящее время ваш JSON представляет собой простой хэш [String:Double], который можно расшифровать даже с помощью JSONSerializer. Однако ваш вопрос об оптимальности обнажает (обычный) вопрос: что вы хотите делать (с этим)?   -  person Patru    schedule 17.04.2018
comment
Я добавляю дополнительную информацию в тело вопроса. Не могли бы вы предоставить фрагмент кода, как использовать JSONSerializer в этом случае? Спасибо   -  person Todd Page    schedule 17.04.2018
comment
Я все еще получаю сообщение об ошибке. Какая ошибка?   -  person vadian    schedule 17.04.2018


Ответы (1)


После некоторых экспериментов моя площадка выглядит следующим образом:

import Cocoa
import Foundation

let jsonData = """
{
"USD_AFN": 70.129997,
"USD_AUD": 1.284793,
"USD_BDT": 82.889999,
"USD_BRL": 3.418294,
"USD_KHR": 4004.99952
}
""".data(using: .utf8)!

do {
    let obj = try JSONSerialization.jsonObject(with:jsonData, options:[])
    print(obj)          // this is an NSDictionary
    if let dict = obj as? [String:Double] {
        print(dict)     // This is not "just" a cast ... more than I thought
    }
}

struct CurrencyRate: Codable {
    var results : [String:Double]
}

// If you use a "results"-key it _must_ be present in your JSON, but it would allow to add methods
let resultsJson = """
{
  "results" : {
    "USD_AFN": 70.129997,
    "USD_AUD": 1.284793,
    "USD_BDT": 82.889999,
    "USD_BRL": 3.418294,
    "USD_KHR": 4004.99952
    }
}
""".data(using: .utf8)!

do {
    let currencyRate = try JSONDecoder().decode(CurrencyRate.self, from: resultsJson)
    print(currencyRate)
}

// this is probably the easiest solution for just reading it
do {
    let rates = try JSONDecoder().decode([String:Double].self, from:jsonData)
    print(rates)
}

// While you could do the following it does not feel "proper"
typealias CurrencyRatesDict = [String:Double]

extension Dictionary where Key == String, Value == Double {
    func conversionRate(from:String, to:String) -> Double {
        let key = "\(from)_\(to)"
        if let rate = self[key] {
            return rate
        } else {
            return -1.0
        }
    }
}

do {
    let currRates = try JSONDecoder().decode(CurrencyRatesDict.self, from:jsonData)
    print(currRates)
    print(currRates.conversionRate(from:"USD", to:"AUD"))
}

Это научило меня нескольким вещам. Я бы не подумал, что NSDictionary (который создается JSONSerialization.jsonObject автоматически и не имеет типов) легко преобразует это в [String:Double], но, конечно, это может дать сбой, и вы должны написать некоторую обработку ошибок, чтобы поймать это.

Преимущество вашего CurrencyRate struct состоит в том, что его можно легко расширить. Поскольку словари structs, из них невозможно извлечь. Как показывает последняя версия, можно добавить условное расширение к Dictionary. Однако это добавит вашу новую функцию к любому Dictionary соответствию сигнатуре, что во многих случаях может быть приемлемым, даже если оно "кажется" неправильным с точки зрения дизайна.

Как видите, в Swift есть целая куча способов справиться с этим. Я бы посоветовал вам использовать протокол Codable и дополнительный ключ. Скорее всего, есть «другие вещи», которые вы захотите сделать с вашим объектом.

person Patru    schedule 17.04.2018