Search code examples
jsonswiftdecodable

Decodable for JSON with two structs under the same tag


I have this json:

{ "stuff": [
 {
 "type":"car",
 "object":{
  "a":66,
  "b":66,
  "c":66 }},
 {
 "type":"house",
 "object":{
  "d":66,
  "e":66,
  "f":66 }},
 {
 "type":"car",
 "object":{
  "a":66,
  "b":66,
  "c":66 }}
]}

As you can see for "car" and "house" there are different "object" structs, but both under the tag "object".

It would be ideal if one ended up with something like

struct StuffItem: Decodable {       
  let type: TheType
  let car: Car
  let house: House
}

Is there some Codable, swifty, way to handle this?


Solution

  • The swiftiest way in my opinion is an enum with associated values

    This is valid JSON

    let jsonString = """
    { "stuff": [
        {
        "type":"car",
        "object":{
            "a":66,
            "b":66,
            "c":66
            }
        },{
        "type":"house",
        "object":{
            "d":66,
            "e":66,
            "f":66
            }
        },{
        "type":"car",
        "object":{
            "a":66,
            "b":66,
            "c":66
            }
        }
    ]}
    """
    

    These are the structs

    struct Root : Decodable {
        let stuff : [Object]
    }
    
    enum Type : String, Decodable { case car, house }
    
    struct Car : Decodable {
        let a, b, c : Int
    }
    
    struct House : Decodable {
        let d, e, f : Int
    }
    
    
    enum Object : Decodable {
        case house(House), car(Car)
        
        private enum CodingKeys : String, CodingKey { case type, object }
        
        init(from decoder: Decoder) throws {
            let container = try decoder.container(keyedBy: CodingKeys.self)
            let type = try container.decode(Type.self, forKey: .type)
            switch type {
            case .car:
                let carData = try container.decode(Car.self, forKey: .object)
                self = .car(carData)
            case .house:
                let houseData = try container.decode(House.self, forKey: .object)
                self = .house(houseData)
            }
        }
    }
    

    And the code to decode the JSON

    do {
        let result = try JSONDecoder().decode(Root.self, from: Data(jsonString.utf8))
        let objects = result.stuff
        for object in objects {
            switch object {
            case .car(let car): print(car)
            case .house(let house): print(house)
            }
        }
    } catch {
        print(error)
    }