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