1

How can I decode the following JSON to a Codable object in Swift?

{"USD":"12.555", "EUR":"11.555"}

Here's the struct i'm using:

struct Prices: Codable {
    var USD: Double
    var EUR: Double
    
    private enum CodingKeys: String, CodingKey {
        case USD = "USD"
        case EUR = "EUR"
    }
    
    init() {
        self.USD = 0.0
        self.EUR = 0.0
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.USD = try container.decode(Double.self, forKey: .USD)
        self.EUR = try container.decode(Double.self, forKey: .EUR)
    }
}

The error I'm getting is

Error typeMismatch(Swift.Double, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "USD", intValue: nil)], debugDescription: "Expected to decode Double but found a string/data instead.", underlyingError: nil))
n83
  • 237
  • 2
  • 15

3 Answers3

2

I think your struct is incorrect and will be hard to maintain if you want to download more currency rates so I suggest a different approach. First create a struct for holding a currency rate

struct CurrencyRate {
    let currency: String
    let rate: Decimal?
}

Then decode the json as a dictionary and use map to convert it into an array of CurrencyRate

var rates = [CurrencyRate]()
do {
    let result = try JSONDecoder().decode([String: String].self, from: json)
    rates = result.map { CurrencyRate(currency: $0.key, rate: Decimal(string: $0.value))}

} catch {
    print(error)
}

Two notes about CurrencyRate

  • You have two currencies in a rate so normally you also have another property named baseCurrency or otherCurrency or something similar but if that other currency always is the same you can omit it.
  • Depending on your use case it might also be a good idea to make a new type for currency properties, Currency
Joakim Danielson
  • 43,251
  • 5
  • 22
  • 52
1

The values you're getting are Strings, so your struct should simply be

struct Prices: Codable {
    let USD: String
    let EUR: String
}

Depending you how you inted to use those price values, converting them to double may be inadvisable, because floating-point math is weird and Decimals may be better suited.

Gereon
  • 17,258
  • 4
  • 42
  • 73
0

You have to decode the values as String and then cast it to Double. So, either Cast String to Double and provide a default value, like this:

init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    self.USD = Double(try container.decode(String.self, forKey: .USD)) ?? 0
    self.EUR = Double(try container.decode(String.self, forKey: .EUR)) ?? 0
}

Or make the properties optional and remove the default values:

struct Prices: Codable {
    var USD, EUR: Double?

init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    self.USD = Double(try container.decode(String.self, forKey: .USD))
    self.EUR = Double(try container.decode(String.self, forKey: .EUR))
}

Note: Also, you don't need CodingKeys as the property names are the same as keys in the data.

Alternatively, you can use the computed property method, like this:

struct Prices: Codable {
    var USDString: String
    var EURString: String

    private enum CodingKeys: String, CodingKey {
        case USDString = "USD"
        case EURString = "EUR"
    }

    var USD: Double? { Double(USDString) }
    var EUR: Double? { Double(EURString) }
}
Frankenstein
  • 15,732
  • 4
  • 22
  • 47