Reputation: 9112
I have N classes all deriving from Base :
class Base {...}
class A : Base {
init(a:Int) {...}
}
class B : Base {
init(b:Int, c:Int) {...}
}
They have been serialized into a json file that contains an array of Base
. It looks like this:
{
"elements" : [
{
"type" : "A",
"a" : 3
},
{
"type" : "B",
"b" : 30,
"c" : 45
}
]
}
When I decode the json, I have a dictionary like one of the 2 above. How can I then create an object of one of my N classes from it ?
Important note : for various reasons, I cannot use Codable
, and JSONEncoder
, JSONDecoder
Upvotes: 0
Views: 177
Reputation: 299275
For classes, it's not quite as straightforward as for structs, but it's still pretty easy to implement this kind of Decodable.
struct ElementContainer: Decodable {
let elements: [Base]
enum CodingKeys: String, CodingKey { case elements }
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.elements = try container.decode([Element].self, forKey: .elements)
.map { $0.base }
}
}
struct Element: Decodable {
let base: Base
enum CodingKeys: String, CodingKey {
case type
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let type = try container.decode(String.self, forKey: .type)
switch type {
case "A":
base = try A(from: decoder)
case "B":
base = try B(from: decoder)
default:
throw DecodingError.dataCorruptedError(forKey: .type,
in: container,
debugDescription: "Unknown type: \(type)")
}
}
}
class Base {}
class A : Base, Decodable {
let a: Int
init(a:Int) {
self.a = a
super.init()
}
}
class B : Base, Decodable {
let b: Int
let c: Int
init(b:Int, c:Int) {
self.b = b
self.c = c
super.init()
}
}
let results = try JSONDecoder().decode(ElementContainer.self, from: json).elements
Structs are a little simpler and you can get rid of the switch statement. It's harder to do this with classes because it introduces required inits that are tedious to implement.
struct ElementContainer: Decodable {
let elements: [Element]
}
struct Element: Decodable {
let base: Base
enum CodingKeys: String, CodingKey {
case type
}
static let mapping: [String: Base.Type] = ["A": A.self, "B": B.self]
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let type = try container.decode(String.self, forKey: .type)
guard let elementType = Element.mapping[type] else {
throw DecodingError.dataCorruptedError(forKey: .type,
in: container,
debugDescription: "Unknown type: \(type)")
}
base = try elementType.init(from: decoder)
}
}
protocol Base: Decodable {}
struct A : Base {
let a: Int
}
struct B : Base {
let b: Int
let c: Int
}
Upvotes: 1
Reputation: 285069
As the JSON contains the type use it to determine the different classes
if let elements = root["elements"] as? [[String:Any]] {
for element in elements {
let type = element["type"] as! String
switch type {
case "A":
let a = element["a"] as! Int
let aInstance = A(a: a)
case "B": // decode b and c and create instance of B
// and so on
}
}
}
Upvotes: 2