Inheritance Decodable

Hi there,


Is there a way to decode a JSON string with model inheritance ?


Let's say I have a Parking class thats holds an array of Vehicle Classes

And depending on the kind of vehicles, Car or Bike

The Decoder instantiate the right type of vehicle base on some logic (here it would be "type: String" equal car or bike)


Here is a sample json

let json = """
{
"name": "CN Tower, Toronto",
"vehicles": [{"type": "car", "make": "Honda", "model": "civic"},{"type": "bike", "make": "Yamaha", "model": "R6"}]
}
""".data(using: .utf8)!


A basic example that fill only vehicle classes into my Parking

class Vehicle: Decodable {
    let model: String
    let make: String
    private let type: String
}
class Bike: Vehicle {}
class Car: Vehicle {}
class Parking: Decodable {
    let name: String
    let vehicles: [Vehicle]
}

do {
    let parking = try JSONDecoder().decode(Parking.self, from: json)
    dump(parking)
} catch let e {
    dump(e)
}


So how should I write a nice code that loop trough vehicles during decoding step, then switch on "type" and instantiate the correct class


class Vehicle: Decodable {
    let model: String
    let make: String
    /
    private let type: String
    enum CodingKeys: String, CodingKey {
        case model
        case make
        case type
    }
    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: Vehicle.CodingKeys.self)
        model = try container.decode(String.self, forKey: .model)
        type = try container.decode(String.self, forKey: .type)
        make = try container.decode(String.self, forKey: .make)
    }
    init?(container: KeyedDecodingContainer<Vehicle.CodingKeys>) throws {
        model = try container.decode(String.self, forKey: .model)
        type = try container.decode(String.self, forKey: .type)
        make = try container.decode(String.self, forKey: .make)
    }
    static func initWith(list: inout UnkeyedDecodingContainer) throws -> [Vehicle] {
        var objects: [Vehicle] = []
        while !list.isAtEnd {
            let acontainer = try list.nestedContainer(keyedBy: Vehicle.CodingKeys.self)
            let type = try acontainer.decode(String.self, forKey: Vehicle.CodingKeys.type)
            switch type {
            case "bike":
                objects.append(try Bike.init(container: acontainer)!)
            case "car":
                objects.append(try Car.init(container: acontainer)!)
            default:
                print("404")
            }
        }
        return objects
    }
}
class Bike: Vehicle {}
class Car: Vehicle {}
class Parking: Decodable {
    let name: String
    let vehicles: [Vehicle]
    enum CodingKeys: String, CodingKey {
        case name
        case vehicles
    }
    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        name = try container.decode(String.self, forKey: .name)
     
        var list = try container.nestedUnkeyedContainer(forKey: Parking.CodingKeys.vehicles)
        vehicles = try Vehicle.initWith(list: &list)
    }
}

I tried something like that in playground ... its "working" but I know its bad !!!


Any ideas are welcome !


Cheers

For all practical purposes, the easiest way to do this is to decode the Parking object as is, then at the end go through the "vehicles" array and replace each object with an instance of the appropriate subclass. It's not worth trying to "optimize" this unless:


— the number of vehicles is very large (say, tens of thousands of instances), or the decoding is done a very large number of times, or


— creating of a single Vehicle instance in very, very expensive, or has intolerable side effects, or


— you don't want to allow creation of (non-sub-class) instances of Vehicles.


For the 2nd and 3rd cases, you can use an intermediate DecodedParking class with a DecodedVehicle array that simply captures the decoded JSON, then use that to create the final Parking/Vehicle instances. For the 1st case, doing the decoding "manually" as in your sample code seems a reasonable performance optimization. The code is a bit bulky (as optimizing code often is), and a bit hard to read casually and maintain, but I don't think it's "bad".

Inheritance Decodable
 
 
Q