0

i am working with a service that have both a websocket for live data and a api for historical data

the JSON looks similar and i would like to decode it to the same object the only difference is that in the live one variable is a number but as a string and with the historical data the number is an int.

and preferably i would like to not have to create 2 almost identical decodable objects.

have anyone tried something similar.

sebbelebbe
  • 74
  • 1
  • 9
  • The best scenario is to ask server side to change the data structure to use the same types Int for all or String for all. In other case please look at my answer. – Andrew Bogaevskyi Jan 31 '22 at 10:25

2 Answers2

0

I think you need a wrapper for that case of some sort. To make it as convenient as possible you could use a property wrapper for this

@propertyWrapper
struct NormalOrStringyInt: Decodable {
    var wrappedValue: Int?

    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        if let value = try? container.decode(Int.self) {
            wrappedValue = value
        } else if
            let string = try? container.decode(String.self),
            let value = Int(string)
        {
            wrappedValue = value
        } else {
            wrappedValue = nil // default value
        }
    }
}

struct Model: Codable { 
    @NormalOrStringyInt var id: Int? 
    var someInt: Int
    var someString: String
    ...
}

let model = try! JSONDecoder().decode(Model, from: data)
let id: Int? = model.id.wrappedValue
fruitcoder
  • 1,073
  • 8
  • 24
0

You have to define a single type (Int or String) for your data structure and use init with Decoder to make a custom parsing.

struct MyData: Decodable {
    let value: Int // Could be Int or String from different services
}

extension MyData {
    enum CodingKeys: String, CodingKey {
        case value
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)

        do {
            value = try container.decode(Int.self, forKey: .value)
        } catch {
            let stringValue = try container.decode(String.self, forKey: .value)

            if let valueInt = Int(stringValue) {
                value = valueInt
            } else {
                var codingPath = container.codingPath
                codingPath.append(CodingKeys.value)
                let debugDescription = "Could not create Int from String \(stringValue) of field \(CodingKeys.value.rawValue)"
                let context = DecodingError.Context(codingPath: codingPath, debugDescription: debugDescription)
                throw DecodingError.dataCorrupted(context)
            }
        }
    }
}

Andrew Bogaevskyi
  • 2,251
  • 21
  • 25