Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Decoding two different JSON responses with one struct using Codable

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
    }
}
like image 207
Nevin Jethmalani Avatar asked Dec 12 '17 17:12

Nevin Jethmalani


2 Answers

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)
        }
    }
}
like image 128
vadian Avatar answered Oct 15 '22 02:10

vadian


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)
}
like image 3
Leo Dabus Avatar answered Oct 15 '22 00:10

Leo Dabus