Как я могу использовать Codable для декодирования ответа JSON, который имеет меняющиеся ключи? - PullRequest
0 голосов
/ 29 мая 2019

У меня есть ответ json от api fixer. И я хочу декодировать его в структуру, которая показана ниже. Проблема заключается в том, что значения «ставок» в json представляют собой массив пар ключ: значение, который имеет разные ключи.

У меня есть вторая структура, которая должна отображаться из этих значений ключа.

Как я могу сделать это, используя Codable?

{
    "success": true,
    "timestamp": 1558883585,
    "base": "EUR",
    "date": "2019-05-26",
    "rates": {
        "AED": 4.116371,
        "AFN": 90.160103,
        "ALL": 122.341655,
        "AMD": 536.359254,
        "ANG": 2.097299,
        "AOA": 368.543773,
        "ARS": 50.418429,
        "AUD": 1.618263,
        "AWG": 2.012124,
        "AZN": 1.910752,
        "BAM": 1.955812,
        "BBD": 2.227793,
        "BDT": 94.245988,
        "BGN": 1.956097,
        "BHD": 0.421705,
        "BIF": 2051.459244,
        "BMD": 1.120649,
        "BND": 1.539664,
        "BOB": 7.729394,
        "BRL": 4.508038,
        "BSD": 1.118587,
        "BTC": 0.00014,
        "BTN": 77.755286,
        "BWP": 12.040813,
        "BYN": 2.323273,
        "BYR": 21964.71167,
        "BZD": 2.254689
    }
}

Если я изменю структуру «ExchangeRate», как показано ниже, она легко декодируется. Но у меня есть требование, чтобы «ставки» были массивом структуры «ConvertedRate».

struct ExchangeRate : Codable {

    let base : String
    let date : String

    let rates : [String:Double] 
}

Это то, что мне нужно.

struct ExchangeRate : Codable {

    let base : String
    let date : String

    let rates : [ConvertedRate] //keys are varied in json

}

struct ConvertedRate : Codable, Comparable{

   let currencyName : String

   let rate : Double

}

Ответы [ 2 ]

3 голосов
/ 29 мая 2019

Вам придется написать пользовательский init(from:), так как вы не используете «поведение по умолчанию».

Дополнительная информация о документации Apple .

Вот возможное решение:

struct ExchangeRate : Codable {

    let base : String
    let date : String

    let rates : [ConvertedRate]

    enum CodingKeys: String, CodingKey {
        case base
        case date
        case rates
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        base = try values.decode(String.self, forKey: .base)
        date = try values.decode(String.self, forKey: .date)
        let additionalInfo = try values.decode([String: Double].self, forKey: .rates)
        print(additionalInfo)
        rates = additionalInfo.map({ ConvertedRate(name: $0.key, rate: $0.value) })
    }
}

struct ConvertedRate : Codable {
    let currencyName : String
    let rate : Double

    init(name: String, rate: Double) {
        self.currencyName = name
        self.rate = rate
    }
}

Примечание. Текущий код может декодировать ваш JSON, как вы хотите, но он не перекодируется так, как было, потому что он копирует структуру Swift:

{
    "base": "EUR",
    "date": "2019-05-26",
    "rates": [{
        "currencyName": "BSD",
        "rate": 1.118587
    }, {
        "currencyName": "BWP",
        "rate": 12.040813
    }, {
        "currencyName": "BYN",
        "rate": 2.3232729999999999
    }, {
        "currencyName": "BBD",
        "rate": 2.2277930000000001
    }, {
        "currencyName": "BOB",
        "rate": 7.7293940000000001
    }, {
        "currencyName": "BAM",
        "rate": 1.9558120000000001
    }, {
        "currencyName": "AUD",
        "rate": 1.618263
    }, {
        "currencyName": "AFN",
        "rate": 90.160103000000007
    }, {
        "currencyName": "BYR",
        "rate": 21964.711670000001
    }, {
        "currencyName": "BRL",
        "rate": 4.508038
    }, {
        "currencyName": "BMD",
        "rate": 1.120649
    }, {
        "currencyName": "BGN",
        "rate": 1.956097
    }, {
        "currencyName": "BHD",
        "rate": 0.421705
    }, {
        "currencyName": "ANG",
        "rate": 2.097299
    }, {
        "currencyName": "AOA",
        "rate": 368.54377299999999
    }, {
        "currencyName": "BZD",
        "rate": 2.2546889999999999
    }, {
        "currencyName": "ARS",
        "rate": 50.418429000000003
    }, {
        "currencyName": "BTC",
        "rate": 0.00013999999999999999
    }, {
        "currencyName": "BIF",
        "rate": 2051.4592440000001
    }, {
        "currencyName": "AWG",
        "rate": 2.012124
    }, {
        "currencyName": "AED",
        "rate": 4.116371
    }, {
        "currencyName": "AMD",
        "rate": 536.35925399999996
    }, {
        "currencyName": "BDT",
        "rate": 94.245987999999997
    }, {
        "currencyName": "BND",
        "rate": 1.5396639999999999
    }, {
        "currencyName": "BTN",
        "rate": 77.755285999999998
    }, {
        "currencyName": "AZN",
        "rate": 1.910752
    }, {
        "currencyName": "ALL",
        "rate": 122.341655
    }]
}
2 голосов
/ 29 мая 2019

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

Нет необходимости принимать Codable в CurrencyRate, потому что экземпляры создаются вручную

let jsonString = """
{
"success": true,
"timestamp": 1558883585,
"base": "EUR",
"date": "2019-05-26",
"rates": {
"AED": 4.116371,"AFN": 90.160103,"ALL": 122.341655,"AMD": 536.359254,"ANG": 2.097299,"AOA": 368.543773,"ARS": 50.418429,"AUD": 1.618263,"AWG": 2.012124,"AZN": 1.910752,"BAM": 1.955812,"BBD": 2.227793,"BDT": 94.245988,"BGN": 1.956097,"BHD": 0.421705,"BIF": 2051.459244,"BMD": 1.120649,"BND": 1.539664,"BOB": 7.729394,"BRL": 4.508038,"BSD": 1.118587,"BTC": 0.00014,"BTN": 77.755286,"BWP": 12.040813,"BYN": 2.323273,"BYR": 21964.71167,"BZD": 2.254689    
}
}
"""

struct ExchangeData : Codable {

    let timestamp : Date
    let base : String
    let date : String

    let rates : [CurrencyRate]

    private enum CodingKeys: String, CodingKey { case timestamp, base, date, rates}

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        timestamp = try container.decode(Date.self, forKey: .timestamp)
        base = try container.decode(String.self, forKey: .base)
        date = try container.decode(String.self, forKey: .date)
        let rateData = try container.decode([String:Double].self, forKey: .rates)
        rates = rateData.map(CurrencyRate.init)
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(timestamp, forKey: .timestamp)
        try container.encode(base, forKey: .base)
        try container.encode(date, forKey: .date)
        var rateData = [String:Double]()
        rates.forEach{ rateData[$0.name] = $0.rate }
        try container.encode(rateData, forKey: .rates)
    }
}

struct CurrencyRate {
    let name : String
    let rate : Double
}

let data = Data(jsonString.utf8)

do {
    let decoder = JSONDecoder()
    decoder.dateDecodingStrategy = .secondsSince1970
    let result = try decoder.decode(ExchangeData.self, from: data)
    print(result)
} catch {
    print(error)
}
...