Reputation: 841
I am fetching data from an API like so:
enum MyError : Error {
case FoundNil(String)
}
struct Crypto : Decodable {
private enum CodingKeys : String, CodingKey { case raw = "RAW" }
let raw : CryptoRAW
}
struct CryptoRAW : Decodable {
private enum CodingKeys : String, CodingKey {
case btc = "BTC"
case xrp = "XRP"
}
let btc : CryptoCURRENCIES?
let xrp : CryptoCURRENCIES?
}
struct CryptoCURRENCIES : Decodable {
private enum CodingKeys : String, CodingKey {
case usd = "USD"
case eur = "EUR"
}
let usd : CryptoCURRENCY?
let eur : CryptoCURRENCY?
}
struct CryptoCURRENCY : Decodable {
let price : Double
let percentChange24h : Double
private enum CodingKeys : String, CodingKey {
case price = "PRICE"
case percentChange24h = "CHANGEPCT24HOUR"
}
}
class CryptoInfo : NSObject {
enum FetchError: Error {
case urlError
case unknownNetworkError
}
func fetchCryptoInfo(forCrypto crypto: String, forCurrency currency: String, _ completion: @escaping (Crypto?, Error?) -> Void) {
let url = URL(string: "https://min-api.cryptocompare.com/data/pricemultifull?fsyms=\(crypto)&tsyms=\(currency)")!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data, error == nil else {
completion(nil, error ?? FetchError.unknownNetworkError)
return
}
do {
let crypto = try JSONDecoder().decode(Crypto.self, from: data); completion(crypto, nil)
} catch let parseError {
completion(nil, parseError)
}
}
task.resume()
}
}
As you can see I can get different things through the same struct: btc
or xrp
in usd
or eur
depending on the parameters passed in the calling function (not included to try to keep the code as short as possible).
When I want to access the Double
value that the API returns here is what I do:
if let price = Crypto.raw.btc?.usd?.price { MainViewController.bitcoinDoublePrice = price }
Everything works great but I have a big optimization problem here: here is what I need to do to get both btc
and xrp
in usd
and eur
:
if let price = Crypto.raw.btc?.usd?.price { MainViewController.bitcoinUSDDoublePrice = price }
if let price = Crypto.raw.btc?.eur?.price { MainViewController.bitcoinEURDoublePrice = price }
if let price = Crypto.raw.xrp?.usd?.price { MainViewController.rippleUSDDoublePrice = price }
if let price = Crypto.raw.xrp?.eur?.price { MainViewController.rippleEURDoublePrice = price }
Now that would be fine if I only had these four, but I need to fetch 25 different cryptos in 5 different currencies + their percentage change over different time frames.
I guess you see the point now.
What can I do to dynamically replace btc
in let price = cryptoInfo.raw.btc?.usd?.price
or eur
in let price = cryptoInfo.raw.xrp?.eur?.price
by passing arguments to the function, or maybe there is a different approach to avoid repetition I couldn't think of?
Example JSON input:
"RAW":{
"BTC":{
"USD":{
"TYPE":"5",
"MARKET":"CCCAGG",
"FROMSYMBOL":"BTC",
"TOSYMBOL":"USD",
"FLAGS":"4",
"PRICE":10248.64,
"LASTUPDATE":1519669598,
"LASTVOLUME":0.14558,
"LASTVOLUMETO":1489.13782,
"LASTTRADEID":"203305344",
"VOLUMEDAY":92548.48622803023,
"VOLUMEDAYTO":924032126.7547476,
"VOLUME24HOUR":107957.56694427232,
"VOLUME24HOURTO":1072399848.5990984,
"OPENDAY":9610.11,
"HIGHDAY":10409.28,
"LOWDAY":9411.82,
"OPEN24HOUR":9466.87,
"HIGH24HOUR":10414.1,
"LOW24HOUR":9396.22,
"LASTMARKET":"Bitfinex",
"CHANGE24HOUR":781.7699999999986,
"CHANGEPCT24HOUR":8.257956431217483,
"CHANGEDAY":638.5299999999988,
"CHANGEPCTDAY":6.644356828381764,
"SUPPLY":16881800,
"MKTCAP":173015490752,
"TOTALVOLUME24H":470883.0751374748,
"TOTALVOLUME24HTO":4791892728.888281
}
}
},
"DISPLAY":{
"BTC":{
"USD":{
"FROMSYMBOL":"Ƀ",
"TOSYMBOL":"$",
"MARKET":"CryptoCompare Index",
"PRICE":"$ 10,248.6",
"LASTUPDATE":"Just now",
"LASTVOLUME":"Ƀ 0.1456",
"LASTVOLUMETO":"$ 1,489.14",
"LASTTRADEID":"203305344",
"VOLUMEDAY":"Ƀ 92,548.5",
"VOLUMEDAYTO":"$ 924,032,126.8",
"VOLUME24HOUR":"Ƀ 107,957.6",
"VOLUME24HOURTO":"$ 1,072,399,848.6",
"OPENDAY":"$ 9,610.11",
"HIGHDAY":"$ 10,409.3",
"LOWDAY":"$ 9,411.82",
"OPEN24HOUR":"$ 9,466.87",
"HIGH24HOUR":"$ 10,414.1",
"LOW24HOUR":"$ 9,396.22",
"LASTMARKET":"Bitfinex",
"CHANGE24HOUR":"$ 781.77",
"CHANGEPCT24HOUR":"8.26",
"CHANGEDAY":"$ 638.53",
"CHANGEPCTDAY":"6.64",
"SUPPLY":"Ƀ 16,881,800.0",
"MKTCAP":"$ 173.02 B",
"TOTALVOLUME24H":"Ƀ 470.88 K",
"TOTALVOLUME24HTO":"$ 4,791.89 M"
}
}
}
}
Thank you!
Upvotes: 1
Views: 164
Reputation: 8718
I think you need to switch to the lower level JSON API given the poorly designed API where there are dozens of possible key names: one for each currency type. Really, you only want the first innermost structure, and peel off the outer shells.
This JSON is truly awful, what with numeric types buried in string types with bizarre spaces. Someone Just Does Not Understand. "$ 4,791.89 M" ??? On planet Earth, we call that: 4791890000 [EDIT: Actually that's the DISPLAY part of the JSON, but that's just a different way of being awful. Totally wasteful.]
If you're a novice, this is a confusing learning example.
See: https://grokswift.com/json-swift-4/
The essence of the lower level API is that it does not create a nice Swift object instantly out of JSON data, but rather a dictionary of key-value pairs, where you have to step carefully and verify that every key that you want is present and every value is the type you want. However, you get total flexibility.
Extracting the data into a Dictionary:
if let outerJSON = try JSONSerialization.jsonObject(with: responseData, options: []) as? [String: Any], ...
Now you can say things like:
// crypto, currency are the input Strings in your code above
if let cryptoJSON: [String: Any] = outerJSON["RAW"] {
if let currencyJSON: [String: Any] = cryptoJSON[crypto] {
if let actualJSON: [String: Any] = currencyJSON[currency] {
let myActualData = TradingData(actualJSON) // Correct O-O
// or to show low level example:
if let price = actualJSON["PRICE"] as? Double {
// Avoid the DISPLAY portion of the JSON and you're OK
// Also the above code implies a problem with the View.
// You probably don't want to maintain an output for every
// combination of crypto and fiat currency.
// Unless you're using a visual data structure that *grows*
// like a UITable. Perhaps:
MainViewController.currencyLabel.text = currency
MainViewController.cryptoLabel.text = crypto
MainViewController.conversionRateLabel.text = "\(price)"
}
}
}
}
Upvotes: 1