使用较差的编码解码JSON(过多的嵌套,许多可能的键,与值混淆的键)

时间:2018-02-26 16:41:06

标签: json swift api struct decoding

我从API中获取数据:

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

正如您所看到的,我可以通过相同的结构获得不同的内容:btcxrp中的usdeur,具体取决于调用函数中传递的参数(不包括试图保持代码尽可能短)。

当我想访问API返回的Double值时,我就是这样做的:

if let price = Crypto.raw.btc?.usd?.price { MainViewController.bitcoinDoublePrice = price }

一切都很好但我在这里有一个很大的优化问题:我需要做的是在btcxrp中同时获得usdeur:< / p>

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 }

如果我只有这四个,那就没关系,但是我需要以5种不同的货币获取25种不同的密码+它们在不同时间范围内的百分比变化。

我猜你现在明白了这一点。

我可以通过向函数传递参数来动态替换btc中的let price = cryptoInfo.raw.btc?.usd?.priceeur中的let price = cryptoInfo.raw.xrp?.eur?.price,或者可能有不同的方法来避免重复我无法想到?

示例JSON输入:

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

谢谢!

1 个答案:

答案 0 :(得分:1)

我认为你需要切换到较低级别的JSON API,因为设计糟糕的API有许多可能的密钥名称:每种货币类型一个。真的,你只需要第一个最里面的结构,然后剥掉外壳。

这个JSON非常糟糕,数字类型埋藏在具有奇异空格的字符串类型中。有人不明白。 “$ 4,791.89 M”??? 在地球上,我们称之为:4791890000 [编辑:实际上这是JSON的DISPLAY部分,但这只是一种可怕的不同方式。完全浪费。]

如果您是新手,这是一个令人困惑的学习示例。

请参阅:https://grokswift.com/json-swift-4/

低级API的本质是它立即用JSON数据创建一个好的Swift对象,而是一个键值对的字典,你必须小心翼翼地验证您想要的每个键是否存在,并且每个值都是您想要的类型。但是,您可以获得完全的灵活性。

将数据提取到词典中:

if let outerJSON = try JSONSerialization.jsonObject(with: responseData, options: []) as? [String: Any], ...

现在你可以这样说:

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