【问题标题】:How to decode variable from json when key is changing according to user input?当密钥根据用户输入发生变化时,如何从 json 解码变量?
【发布时间】:2018-07-03 12:12:18
【问题描述】:

我正在尝试使用 Swift 4 中的 JSONDecoder() 解析来自 CoinmarketCap 的一些 JSON 响应。但问题是来自 json 的响应会根据用户输入而变化。例如,如果用户想要以 eur 为单位的价格,则输出如下:

[
    {
        "price_eur": "9022.9695444"
    }
]

但如果用户想要以 gbp 为单位的价格:

[
    {
        "price_gbp": "7906.8032145"
    }
]

所以问题是如果变量(json 键)名称发生变化,我应该如何制作从 Decodable 继承的结构?

【问题讨论】:

    标签: json swift swift4 codable jsondecoder


    【解决方案1】:

    如果您有少量可能的键,您可以执行以下操作

    struct Price: Decodable {
    
        var value: String
    
        enum CodingKeys: String, CodingKey {
            case price_eur
            case price_gbp
            case price_usd
        }
    
        init(from decoder: Decoder) throws {
            let container = try decoder.container(keyedBy: CodingKeys.self)
            do {
                value = try container.decode(String.self, forKey: .price_eur)
            } catch {
                do {
                    value = try container.decode(String.self, forKey: .price_gbp)
                } catch {
                    value = try container.decode(String.self, forKey: .price_usd)
                }
            }
        }
    }
    
    let data = try! JSONSerialization.data(withJSONObject: ["price_gbp": "10.12"], options: [])
    let price = try JSONDecoder().decode(Price.self, from: data)
    

    否则,您将需要手动解析数据。

    【讨论】:

    • 您实际上不需要手动解析数据,可以使用动态键同时仍符合Codable 协议。请参阅我的答案以了解如何操作。
    【解决方案2】:

    您可以通过为您的结构创建自定义 init(from:) 方法来解码动态密钥,然后使用两组编码密钥,一个 enum 包含编译时已知的所有密钥,另一个 struct 您初始化使用通过用户输入生成的动态键(包含货币名称)。

    在您的自定义 init(from:) 方法中,您只需使用它们各自的键解码每个属性。

    let chosenCurrency = "gbp"
    
    struct CurrencyResponse: Decodable {
        let name:String
        let symbol:String
        let price:String
        private static var priceKey:String {
            return "price_\(chosenCurrency)"
        }
    
        private enum SimpleCodingKeys: String, CodingKey {
            case name, symbol
        }
    
        private struct PriceCodingKey : CodingKey {
            var stringValue: String
            init?(stringValue: String) {
                self.stringValue = stringValue
            }
            var intValue: Int?
            init?(intValue: Int) {
                return nil
            }
        }
    
        init(from decoder:Decoder) throws {
            let values = try decoder.container(keyedBy: SimpleCodingKeys.self)
            name = try values.decode(String.self, forKey: .name)
            symbol = try values.decode(String.self, forKey: .symbol)
            let priceValue = try decoder.container(keyedBy: PriceCodingKey.self)
            price = try priceValue.decode(String.self, forKey: PriceCodingKey(stringValue:CurrencyResponse.priceKey)!)
        }
    }
    
    do {
        let cryptoCurrencies = try JSONDecoder().decode([CurrencyResponse].self, from: priceJSON.data(using: .utf8)!)
    } catch {
        print(error)
    }
    

    测试 JSON:

    let priceJSON = """
        [
        {
        "id": "bitcoin",
        "name": "Bitcoin",
        "symbol": "BTC",
        "rank": "1",
        "price_\(chosenCurrency)": "573.137",
        "price_btc": "1.0",
        "24h_volume_\(chosenCurrency)": "72855700.0",
        "market_cap_\(chosenCurrency)": "9080883500.0",
        "available_supply": "15844176.0",
        "total_supply": "15844176.0",
        "percent_change_1h": "0.04",
        "percent_change_24h": "-0.3",
        "percent_change_7d": "-0.57",
        "last_updated": "1472762067"
        },
        {
        "id": "ethereum",
        "name": "Ethereum",
        "symbol": "ETH",
        "rank": "2",
        "price_\(chosenCurrency)": "12.1844",
        "price_btc": "0.021262",
        "24h_volume_\(chosenCurrency)": "24085900.0",
        "market_cap_\(chosenCurrency)": "1018098455.0",
        "available_supply": "83557537.0",
        "total_supply": "83557537.0",
        "percent_change_1h": "-0.58",
        "percent_change_24h": "6.34",
        "percent_change_7d": "8.59",
        "last_updated": "1472762062"
    }
    ]
    """
    

    【讨论】:

    • 这是一个很棒的答案,非常感谢。据我了解,那么“chosenCurrency”必须是一个全局变量?
    • @TarvoMäesepp 不用担心,很高兴我能帮上忙。不,chosenCurrency 实际上不需要是一个全局变量,你可以在任何地方定义它,只要你可以从 CurrencyResponse 的初始化器和你的 viewcontroller 类访问它,用户输入来自哪里。我刚刚在操场上测试了我的代码,所以这是定义chosenCurrency 的最简单的解决方案。
    • struct 之外添加chosenCurrency 效果不佳。例如,如果您想解析tableview,那么更新chosenCurrency 就会迟到。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-06-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-28
    • 2021-10-03
    • 2021-11-14
    相关资源
    最近更新 更多