【问题标题】:Decoding two different JSON responses with one struct using Codable使用 Codable 用一个结构解码两个不同的 JSON 响应
【发布时间】:2017-12-12 17:36:57
【问题描述】:

我从两个不同的端点获取数据。一个端点返回这样的订单:

{
  "price":"123.49",
  "quantity":"4",
  "id":"fkuw-4834-njk3-4444",
  "highPrice":"200",
  "lowPrice":"100"
}

另一个端点返回这样的顺序:

{
  "p":"123.49", //price 
  "q":"4", //quantity
  "i":"fkuw-4834-njk3-4444" //id
}

我想使用相同的 Codable 结构来解码两个 JSON 响应。我该怎么做?是否可以使用一个结构来做到这一点,还是我必须创建第二个结构?这是第一个 JSON 返回的结构现在的样子:

struct SimpleOrder:Codable{
    var orderPrice:String
    var orderQuantity:String
    var id:String
    var highPrice:String

    private enum CodingKeys: String, CodingKey {
        case orderPrice = "price"
        case orderQuantity = "quantity"
        case id
        case highPrice
    }
}

【问题讨论】:

  • 我的建议是为每个端点设置 2 个单独的结构,并将它们都转换为您要在应用程序中使用的类型。它可以更好地将您的应用与您的 API 分离。

标签: json swift struct swift4 codable


【解决方案1】:

您可以这样做,但您必须将所有属性声明为可选并编写自定义初始化程序

struct SimpleOrder : Decodable {
    var orderPrice : String?
    var orderQuantity : String?
    var id : String?
    var highPrice : String?

    private enum CodingKeys: String, CodingKey {
        case orderPrice = "price"
        case orderQuantity = "quantity"
        case id
        case highPrice
        case i, q, p
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        orderPrice = try container.decodeIfPresent(String.self, forKey: .orderPrice)
        orderPrice = try container.decodeIfPresent(String.self, forKey: .p)
        orderQuantity = try container.decodeIfPresent(String.self, forKey: .orderQuantity)
        orderQuantity = try container.decodeIfPresent(String.self, forKey: .q)
        id = try container.decodeIfPresent(String.self, forKey: .id)
        id = try container.decodeIfPresent(String.self, forKey: .i)
        highPrice = try container.decodeIfPresent(String.self, forKey: .highPrice)
    }
}

或者使用两个不同的键集,检查其中一个键的出现并选择适当的键集。好处是pricequantityid可以声明为非可选

struct SimpleOrder : Decodable {
    var orderPrice : String
    var orderQuantity : String
    var id : String
    var highPrice : String?

    private enum CodingKeys: String, CodingKey {
        case orderPrice = "price"
        case orderQuantity = "quantity"
        case id
        case highPrice
    }

    private enum AbbrevKeys: String, CodingKey {
        case i, q, p
    }

    init(from decoder: Decoder) throws {
        let cContainer = try decoder.container(keyedBy: CodingKeys.self)
        if let price = try cContainer.decodeIfPresent(String.self, forKey: .orderPrice) {
            orderPrice = price
            orderQuantity = try cContainer.decode(String.self, forKey: .orderQuantity)
            id = try cContainer.decode(String.self, forKey: .id)
            highPrice = try cContainer.decode(String.self, forKey: .highPrice)
        } else {
            let aContainer = try decoder.container(keyedBy: AbbrevKeys.self)
            orderPrice = try aContainer.decode(String.self, forKey: .p)
            orderQuantity = try aContainer.decode(String.self, forKey: .q)
            id = try aContainer.decode(String.self, forKey: .i)
        }
    }
}

【讨论】:

  • 你发布的这个方法会减慢它的解码速度,对吧?有没有办法使用两组编码键,然后根据我们调用的 api 告诉 codbal 使用哪一组?
  • 速度差异可以忽略不计。两个键集的问题是如何告诉班级使用哪一个。您不能编写带有附加参数的自定义初始化程序。
  • 完美。所以这是一种在不影响性能的情况下完全可以接受的方法吗?
  • @NevinJethmalani 是的。
  • 我添加了一个替代建议。
【解决方案2】:

无需为您的 Codable 结构创建自定义初始化程序,您只需将属性设为可选即可。我建议创建一个只读计算属性,该属性将使用 nil 合并运算符返回价格和数量,因此它将始终返回一个或另一个:

struct Order: Codable {
    let price: String?
    let quantity: String?
    let id: String?
    let highPrice: String?
    let lowPrice: String?
    let p: String?
    let q: String?
    let i: String?
}

extension Order {
    var orderPrice: Double {
        return Double(price ?? p ?? "0") ?? 0
    }
    var orderQuantity: Int {
        return Int(quantity ?? q ?? "0") ?? 0
    }
    var userID: String {
        return id ?? i ?? ""
    }
}

测试:

let ep1 = Data("""
{
    "price":"123.49",
    "quantity":"4",
    "id":"fkuw-4834-njk3-4444",
    "highPrice":"200",
    "lowPrice":"100"
}
""".utf8)

let ep2 = Data("""
{
    "p":"123.49",
    "q":"4",
    "i":"fkuw-4834-njk3-4444"
}
""".utf8)

do {
    let order = try JSONDecoder().decode(Order.self, from: ep2)
    print(order.orderPrice)    // "123.49\n"
    print(order.orderQuantity) // "4\n"
    print(order.userID)        // "fkuw-4834-njk3-4444\n"
} catch {
    print(error)
}

【讨论】:

  • 没有。如果它不存在,它将忽略。
  • 很好的答案@LeoDabus!保持懒惰是最好的方法。理论上,多个对象最终可能来自同一个构造函数,甚至可能是基于使用对结构和类进行分组的能力。而且,这几乎可以弥补 Structs 继承的缺点
猜你喜欢
  • 2023-04-04
  • 2023-04-05
  • 2020-07-29
  • 1970-01-01
  • 1970-01-01
  • 2021-10-15
  • 2020-01-14
  • 1970-01-01
  • 2020-02-01
相关资源
最近更新 更多