5

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
    }
}
1
  • 1
    My recommendation would be to have 2 separate structs for each endpoint and convert both of them to the Type you want to use in your app. It better de-coubles your app from your APIs. Commented Dec 12, 2017 at 19:15

2 Answers 2

5

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)
        }
    }
}
Sign up to request clarification or add additional context in comments.

9 Comments

this method you posted will slow down the speed at which it will decode right? there is no way to use two sets of coding keys and then tell codbale which ones to use depending on the api we are calling?
The speed difference is negligible. The problem with two key sets is how to tell the class which one to use. You can't write a custom initializer with an additional parameter.
perfect. so this is a perfectly acceptable way to do it without compromising on performance right?
@NevinJethmalani Yes, it is.
@Ja5onHoffman Consider that highPrice is declared optional (?).
|
3

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)
}

2 Comments

No. It will just ignore if it is not present.
Great answer @LeoDabus ! Keeping it lazy is the best way to go. in theory, multiple objects could end up coming from the same constructor, maybe even the capability of grouping structs and classes based on use. And, this could pretty much compensate for Structs inheritance shortcomings

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.