I am getting data from two different endpoints. One endpoints returns an order like this:
{
"price":"123.49",
"quantity":"4",
"id":"fkuw-4834-njk3-4444",
"highPrice":"200",
"lowPrice":"100"
}
and the other endpoint returns the order like this:
{
"p":"123.49", //price
"q":"4", //quantity
"i":"fkuw-4834-njk3-4444" //id
}
I want to use the same Codable struct to decode both JSON responses. How would I do that? Is it possible to do that using one struct or would I have to create a second struct? Here is what the struct looks like right now for the first JSON return:
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
}
}
You can do that but you have to declare all properties as optional and write a custom initializer
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)
}
}
Alternatively use two different key sets, check the occurrence of one of the keys and choose the appropriate key set. The benefit is that price
, quantity
and id
can be declared as non-optional
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)
}
}
}
There is no need to create a custom initializer for your Codable structure, all you need is to make the properties optional. What I recommend is to create a read only computed property that would return the prices and quantities using a nil coalescing operator so it will always return one or another:
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 ?? ""
}
}
Testing:
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)
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With