【问题标题】:Parsing CoinMarketCap API using Swift's Decodable protocol使用 Swift 的可解码协议解析 CoinMarketCap API
【发布时间】:2018-10-30 02:30:49
【问题描述】:

我已阅读 How to decode a nested JSON struct with Swift Decodable protocol? 它没有解决我将字符串文字数值用作根字典的特定用例。

还有How to decode a nested JSON struct with Swift Decodable protocol? Imanou Petit 的回答。 Can't decode JSON data from API Leo Dabus 的回答。

货币本身就是由 data 字典中的字符串数字表示的字典,所以这让我很反感。我正在寻找使用枚举的最 Swifty 4 模型,可以很容易地看到哪些容器对应于哪些字典。

附言大卫贝瑞给出了一个很好的答案,我已经在下面实现了。如果其他人有其他方法来获得相同的结果,我很乐意看到不同的建议。也许有一些新的 Swift 4 方法还不为人所知或其他设计模式。

代码

struct RawServerResponse {

    enum RootKeys: String, CodingKey {
        case data
        case btc = "1"
        case eth = "1027"
        case iota = "1720"
        case ripple = "52"
        case neo = "1376"
        case quotes
        case USD
    }

    enum BaseKeys: String, CodingKey {
        case id, name, symbol, maxSupply = "max_supply"
    }

    enum QuotesKeys: String, CodingKey {
        case USD
    }

    enum USDKeys: String, CodingKey {
        case price, marketCap = "market_cap"
    }

    let data: String
    let id: Int
    let name: String
    let symbol: String
    let maxSupply: Double
    let price: Double
    let marketCap: Double
}

extension RawServerResponse: Decodable {

    init(from decoder: Decoder) throws {
        // data
        let container = try decoder.container(keyedBy: RootKeys.self)
        data = try container.decode(String.self, forKey: .data)

        // id
        let idContainer = try container.nestedContainer(keyedBy: BaseKeys.self, forKey: .data)
        id = try idContainer.decode(Int.self, forKey: .id) 

        // price
        let priceContainer = try container.nestedContainer(keyedBy: USDKeys.self, forKey: .USD)
        price = try priceContainer.decode(Double.self, forKey: .price)

    }

}

API / JSON https://api.coinmarketcap.com/v2/ticker/

{
"data": {
    "1": {
        "id": 1, 
        "name": "Bitcoin", 
        "symbol": "BTC", 
        "website_slug": "bitcoin", 
        "rank": 1, 
        "circulating_supply": 17041575.0, 
        "total_supply": 17041575.0, 
        "max_supply": 21000000.0, 
        "quotes": {
            "USD": {
                "price": 8214.7, 
                "volume_24h": 5473430000.0, 
                "market_cap": 139991426153.0, 
                "percent_change_1h": 0.09, 
                "percent_change_24h": 2.29, 
                "percent_change_7d": -2.44
            }
        }, 
        "last_updated": 1526699671
    }, 
    "1027": {
        "id": 1027, 
        "name": "Ethereum", 
        "symbol": "ETH", 
        "website_slug": "ethereum", 
        "rank": 2, 
        "circulating_supply": 99524121.0, 
        "total_supply": 99524121.0, 
        "max_supply": null, 
        "quotes": {
            "USD": {
                "price": 689.891, 
                "volume_24h": 2166100000.0, 
                "market_cap": 68660795252.0, 
                "percent_change_1h": 0.13, 
                "percent_change_24h": 2.51, 
                "percent_change_7d": 2.54
            }
        }, 
        "last_updated": 1526699662
    }
}

【问题讨论】:

    标签: json swift


    【解决方案1】:

    我会对数据采取一种更简单的方法,将“数据”视为键控、相同响应的集合,同样,“引用”是引用的键控集合。

    struct RawServerResponse : Decodable {
        enum Keys : String, CodingKey {
            case data = "data"
        }
    
        let data : [String:Base]
    }
    
    struct Base : Decodable {
        enum CodingKeys : String, CodingKey {
            case id = "id"
            case name = "name"
            case symbol = "symbol"
            case maxSupply = "max_supply"
            case quotes = "quotes"
        }
    
        let id : Int64
        let name : String
        let symbol : String
        let maxSupply : Double?
        let quotes : [String:Quote]
    }
    
    struct Quote : Decodable {
        enum CodingKeys : String, CodingKey {
            case price = "price"
            case marketCap = "market_cap"
        }
    
        let price :  Double
        let marketCap : Double
    }
    

    然后,如果您确实需要从那些更简单的结构中访问单个键值,您可以提供计算访问器:

    extension RawServerResponse {
        enum BaseKeys : String {
            case btc = "1"
            case eth = "1027"
        }
    
        var eth : Base? { return data[BaseKeys.eth.rawValue] }
        var btc : Base? { return data[BaseKeys.btc.rawValue] }
    }
    

    同样,您可以为货币创建类似的访问器:

    extension Base {
        enum Currencies : String {
            case usd = "USD"
        }
    
        var usd : Quote? { return quotes[Currencies.usd.rawValue]}
    }
    

    一旦您解决了这部分问题,那么原始问题中的this link 将向您展示如何使结构变平(如果这是您想要的)。本质上,它归结为将计算属性更改为您将设置为构造函数的一部分的 let 属性。

    【讨论】:

    • 是的,我只是输入答案而不是从 Xcode 复制的那些部分 :) 并且 var 语句需要在明显的位置插入返回。
    • 我将扩展更新为正确的类和语法。
    • 像魅力一样工作。谢谢。我花了一秒钟才意识到“扁平化”意味着创建值属性和初始化器。
    • 忘记了一个问题。如果我有一个包含来自 API 的所有数据的对象,即 let allData = response.apiData 我如何仅循环遍历“符号”键?我问的原因是因为使用此 API,每个“符号”都存在于父根数据字典中的单独货币字典中。目前我只能通过btcSymbol = rawResponse.btc?.symbol 检索单独的货币符号。谢谢
    • 最简单的方法是使用map:for symbol in resp.data.values.map({ $0.symbol })
    猜你喜欢
    • 2020-02-09
    • 2020-01-13
    • 1970-01-01
    • 1970-01-01
    • 2018-11-26
    • 1970-01-01
    • 1970-01-01
    • 2019-03-24
    • 1970-01-01
    相关资源
    最近更新 更多