0

I am trying to decode JSON object using Codable with Swift 4:

{
USD: {
"15m": 9977.49,
last: 9977.49,
buy: 9979.36,
sell: 9975.62,
symbol: "$"
    },
AUD: {
"15m": 13181.69,
last: 13181.69,
buy: 13184.16,
sell: 13179.22,
symbol: "$"
    },
TBD: {
"15m": 13181.69,
last: 13181.69,
buy: 13184.16,
sell: 13179.22,
symbol: "$"
    }
}

This is what I've done so far with a model of an object:

struct Currency: Codable {

    let fifteenMin: Double?
    let last: Double?
    let buy: Double
    let sell: Double
    let symbol: String

    enum CodingKeys: String, CodingKey {
        case fifteenMin = "15m"
        case last
        case buy
        case sell
        case symbol
    }
}

And this is how I decode that:

var currencyName = [String]()
var currenciesArray = [Currency]()


func fetchCurrencyData() {
    guard let urlString = API.RateURL.absoluteString else { return }
    guard let url = URL(string: urlString) else { return }
    let jsonData = try! Data(contentsOf: url)
    let decoder = JSONDecoder()
    do {
        let currenciesData = try decoder.decode([String: Currency].self, from: jsonData)
        for currency in currenciesData {
            self.currencyName.append(currency.key)
            self.currenciesArray.append(currency.value)
        }
    } catch let err {
        print(err.localizedDescription)
    }
}

So when I want to populate rows in tableView with that Data I use "for loop" and append "USD", "AUD" and so on in one array and Currency object in another array.

What is the better way of fetching data and populating Currency object. Cuz I am pretty sure that fetching and appending the same object of JSON into two separate arrays isn't good practice I assume.

If my question isn't clear I can explain in more details what I would like to achieve.

Thank you.

Pavel Bogart
  • 405
  • 5
  • 16

2 Answers2

2

It's quite annoying (and error-prone) to maintain multiple arrays. I recommend to include the name in the struct.

Add this line in Currency

var name = ""

After decoding the JSON sort the keys, get the corresponding value and set the name to the key

var currenciesArray = [Currency]()

let currencies = try decoder.decode([String: Currency].self, from: jsonData)

for key in currencies.keys.sorted() {
    var currency = currencies[key]!
    currency.name = key
    currenciesArray.append(currency)
}
print(currenciesArray)
vadian
  • 274,689
  • 30
  • 353
  • 361
1

Shouldn't the currency name be inside the Currency object? That would be much easier to manipulate.

But if you must use that format, you can create an array of tuples or dictionaries.

var currenciesArray = [(String, Currency)]() 

and then append like this:

for currency in currenciesData {
    currenciesArray.append((currency.key, currency.value))
}

p.s. : You need to add quotes(") on the string values inside the JSON, or it will be invalid.

****Added*****

The code below will encode and decode a JSON similar to the one you mentioned:

struct Price: Codable {

    let fifteenMin: Double?
    let last: Double?
    let buy: Double
    let sell: Double
    let symbol: String

    enum CodingKeys: String, CodingKey {
        case fifteenMin = "15m"
        case last
        case buy
        case sell
        case symbol
    }
}

struct Currencies: Codable {
    var USD: Price? 
    var JPY: Price?
    var EUR: Price?
    var AUD: Price?
}

let usdPrice = Price(fifteenMin: 10, last: 11, buy: 12, sell: 13, symbol: "$")
let jpyPrice = Price(fifteenMin: 10, last: 11, buy: 12, sell: 13, symbol: "¥")
var currencies = Currencies()
currencies.USD = usdPrice


// Encode data
let jsonEncoder = JSONEncoder()
do {
    let jsonData = try jsonEncoder.encode(currencies)
    let jsonString = String(data: jsonData, encoding: .utf8)
    print("JSON String : " + jsonString!)
    do {
        // Decode data to object
        let jsonDecoder = JSONDecoder()
        let currencies = try jsonDecoder.decode(Currencies.self, from: jsonData)

        print("USD: \(currencies.USD)")
        print("JPY: \(currencies.JPY)")
    }
    catch {
    }

}
catch {
}
Diogo Souza
  • 370
  • 2
  • 12
  • Yeah, I was thinking of inserting currency name inside of Currency object, but with Codable where I need to use names of properties as keys to decode JSON I am not sure how to do that cuz currency name has no key in JSON Object. – Pavel Bogart Dec 04 '17 at 07:00
  • I think you should create one struct called Currency and another called Price (for example), and use Price as a property on Currency, as well as a property called "name" (which will be USD, JPY, AUD, etc). – Diogo Souza Dec 04 '17 at 07:05
  • check the edited answer. I added a code that will output a JSON similar to the one you mentioned, and then it will decode the JSON back to an object. – Diogo Souza Dec 04 '17 at 07:34
  • Oh thank you for that answer. However it's still unclear for me. You created two properties and called them as the names of currencies but I have 22 currencies.. so I can't create 22 properties in the Currencies struct Maybe i misunderstood your explanation though. Correct me please if I am wrong. – Pavel Bogart Dec 04 '17 at 07:38
  • Maybe it's not the best solution, but that's what I could do to use that specific JSON and decode using Codable. But why not? You can create a struct with all possible currencies (as optionals) and decode all available ones. (I edited the answer) – Diogo Souza Dec 04 '17 at 07:49
  • Of course I appreciate your help, but it isn't a flexible way of doing this. What if names of currencies will get changed with time, then the app will crash and also you will spend time to find all changed properties in the project. So I don't recommend implementing this. :) – Pavel Bogart Dec 04 '17 at 07:58
  • I guess it depends on how your app works. But your app wouldn't crash if a new currency was added, it just wouldn't decode the new currency(new currencies doesn't get created so often, so I don't see a big problem). If you explain how your app works, maybe I can give you a better solution. – Diogo Souza Dec 04 '17 at 08:02
  • Depending on your use case, maybe the tuple solution that I gave in the beginning is better than trying to put everything in an object. – Diogo Souza Dec 04 '17 at 08:07