7

Looking for some input as to how you would handle the scenario I recently ran into.

I have been using Swift 4s Codable with success but today noticed a crash that I didn't expect. The API that I am working with, says it returns a boolean for the key manage_stock.

My stubbed struct looks like:

struct Product: Codable {
    var manage_stock: Bool?
}

That works fine, the problem is that the API sometimes returns a string instead of a boolean.

Because of this, my decode fails with:

Expected to decode Bool but found a string/data instead.

The string only ever equals "parent" and I want it to equate to false.

I am also fine with changing my struct to var manage_stock: String? if that makes things easier to bring the JSON data in from the API. But of course, if I change that, my error just changes to:

Expected to decode String but found a number instead.

Is there a simple way to handle this mutation or will I need to lose all the automation that Codable brings to the table and implement my own init(decoder: Decoder).

Cheers

mg87
  • 477
  • 2
  • 5
  • 9
  • You have to *implement my own `init(decoder: Decoder)`*. Alternatively ask the owner of the API to send consistent data – vadian Feb 05 '18 at 11:26
  • 7
    generally speaking, such chaotic API is supposed to be fixed inside the API, rather than patching the API's clients, because you __cannot__ patch your app for every stupid anomaly what the web-devs committed; therefore they (the API and its devs) need to be stuck with strict (optionally optional) types; could you negotiate with them instead of trying to cover their crap? because such issue __must be__ solved on the API level, not on the app level. – holex Feb 05 '18 at 12:17
  • 1
    maybe related/useful: https://stackoverflow.com/questions/48297263/how-to-use-any-in-codable-type/48297432#48297432 – Scriptable Feb 05 '18 at 12:58

1 Answers1

22

Since you can't always be in control of the APIs you're working with, here's one simple way to solve this with Codable directly, by overriding init(from:):

struct Product : Decodable {
    // Properties in Swift are camelCased. You can provide the key separately from the property.
    var manageStock: Bool?

    private enum CodingKeys : String, CodingKey {
        case manageStock = "manage_stock"
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        do {
            self.manageStock = try container.decodeIfPresent(Bool.self, forKey: .manageStock)
        } catch DecodingError.typeMismatch {
            // There was something for the "manage_stock" key, but it wasn't a boolean value. Try a string.
            if let string = try container.decodeIfPresent(String.self, forKey: .manageStock) {
                // Can check for "parent" specifically if you want.
                self.manageStock = false
            }
        }
    }
}

See Encoding and Decoding Custom Types for more info on this.

Itai Ferber
  • 28,308
  • 5
  • 77
  • 83