Search code examples
iosswiftjson-deserializationcodabledecodable

Decode json array data with different datatypes in IOS using struct decodable swift 4/5


I've tried to construct struct and decode using it, but it only works if all the datatype is same as defined

For example the below code works fine:

{"key1": "stringValue", "key2": intValue, "key3": ["stringData1", "stringData2", "stringData3"]}
struct User: Decodable               
{
    var key1: String
    var key2: Int
    var key3: [String]
}

let decoder = JSONDecoder()
let decodedJsonData = try decoder.decode(User.self, from: data)
print(decodedJsonData)

What should I do to decode if key3 contains different data types?

{"key1": "stringValue", "key2": intValue, "key3": ["stringData1", IntData, FloatData]}

Solution

  • Use enums with associated values:

    struct User: Codable {
        let command, updated: Int
        let data: [Datum]
    }
    
    enum Datum: Codable {
        case double(Double)
        case string(String)
    
        init(from decoder: Decoder) throws {
            let container = try decoder.singleValueContainer()
            if let x = try? container.decode(Double.self) {
                self = .double(x)
                return
            }
            if let x = try? container.decode(String.self) {
                self = .string(x)
                return
            }
            throw DecodingError.typeMismatch(Datum.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for Datum"))
        }
    
        func encode(to encoder: Encoder) throws {
            var container = encoder.singleValueContainer()
            switch self {
            case .double(let x):
                try container.encode(x)
            case .string(let x):
                try container.encode(x)
            }
        }
    }
    

    to get at the individual values in data, use code like this:

    let json = """
        {"command": 1, "updated": 2, "data": ["stringData1", 42, 43]}
        """.data(using: .utf8)
    
    do {
        let user = try JSONDecoder().decode(User.self, from: json!)
    
        for d in user.data {
            switch d {
            case .string(let str): print("String value: \(str)")
            case .double(let dbl): print("Double value: \(dbl)")
            }
        }
    } catch {
        print(error)
    }