-1

I'm trying to parse JSON with currency rates contains dynamic keys and a dynamic number of values. Output depends on input parameters, such as Base currency and several currencies to compare.

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

Also, I should be able to change Base currency and currencies to compare, and change number of currencies to compare. I already tried this answer.

What is the optimal way to handle it?

Thanks

Additional info

So, I made the struct without the initializer

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

and trying to decode it

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

I'm still getting the error.

Eventually, I just need an array of currency rates (values) to populate it in a Table View.

Todd Page
  • 51
  • 5
  • I'd keep the dictionary (decode into `[String:Double]`) – vadian Apr 16 '18 at 19:57
  • I tried to use this struct `struct CurrencyRate: Codable { var results : [String:Double] init(results: [String:Double]) { self.results = results }` and `let results = try decoder.decode(CurrencyRate.self, from: dataToDecode)` , but it catching an error. Probably my struct is not correct for this situation? – Todd Page Apr 16 '18 at 20:29
  • Is this the entire JSON? If yes the code is supposed to work. You don't need the initializer in the struct, not even without conforming to `Codable` – vadian Apr 16 '18 at 20:36
  • Yes, this is the entire JSON, but it may contain any number of objects and any combination of keys (XXX_XXX). So, I made the struct without the initializer `struct CurrencyRate: Codable { var results : [String:Double] }` and trying to decode it `do { let results = try decoder.decode(CurrencyRate.self, from: dataToDecode) print(results) } catch { print("Error") }` I'm still getting the error. What am I doing wrong? – Todd Page Apr 17 '18 at 01:03
  • It is a bad idea to put more than a line of code into a comment. Add it to the question if you like to do it that way. Your `JSON` is lacking the `results` key which is why it would not parse. Currently your `JSON` is a simple hash `[String:Double]` which could be decoded even with a `JSONSerializer`. However your question of "optimal" bares the (usual) question: "What do you want to do (with it)?" – Patru Apr 17 '18 at 01:56
  • I put additional info in the question body. Could you please provide a code snippet how to use JSONSerializer in this case? Thanks – Todd Page Apr 17 '18 at 02:42
  • *I'm still getting the error.* What error? – vadian Apr 17 '18 at 07:06

1 Answers1

1

After some experimentation my Playground looks as follows:

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

This taught me a few things. I would not have thought that a NSDictionary (which is produced by JSONSerialization.jsonObject automatically and has no types) converts this easily into a [String:Double], but of course it might fail and you should write some error handling to catch it.

Your CurrencyRate struct would have the advantage to allow easy extensions. Since Dictionaries are structs it is not possible to derive from them. As the last version illustrates it is possible to add a conditional extension to a Dictionary. However this would add your new function to any Dictionary matching the signature which might be acceptable in many cases even though it 'feels' wrong from the design perspective.

As you can see there is a whole bunch of ways to deal with this in Swift. I would suggest you use the Codable protocol and an additional key. Most probably there are "other things" you will want to do with your object.

Patru
  • 4,481
  • 2
  • 32
  • 42