skip to Main Content

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.

2

Answers


  1. 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
    
    Login or Signup to reply.
  2. 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)
                }
            }
        }
    }
    
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search