Say I have parent class A: Codable
with subclasses B1: A
and B2: A
. A different class Main: Codable
in my application has a pointer to an A
which can either be a B1
or a B2
but cannot be an A
(I'm effectively treating A
as an abstract class).
When I am decoding a Main
, I run into an issue where it is incorrectly being decoded to an abstract A
rather than a B1
or B2
, even though the value store in A
will always be a B1
or a B2
. I have tried implementing custom init(from decoder: Decoder)
and func encode(to encoder: Encoder)
s in the subclasses but when I step through Main
's decode logic in my running app, I never see those subclasses' implementations being called.
Is this because Main
has an A
and has no idea to even attempt to decode it as a B1
or a B2
? Do I need to call those subclasses decoders specifically? If the latter is that case, those subclasses decoders couldn't call the parent decoder because that would create an infinite loop.
Here is what my code currently looks like:
class Main: Codable {
let a: A
}
class A: Codable {
}
class B1: A {
let b1Only: Int
private enum CodingKeys: String, CodingKey {
case b1Only
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
b1Only = try container.decode(Int.self, forKey: .b1Only)
try super.init(from: decoder)
}
override func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.b1Only, forKey: .b1Only)
try super.encode(to: encoder)
}
}
class B2: A {
let b2Only: Int
private enum CodingKeys: String, CodingKey {
case b2Only
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
b2Only = try container.decode(Int.self, forKey: .b2Only)
try super.init(from: decoder)
}
override func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.b2Only, forKey: .b2Only)
try super.encode(to: encoder)
}
}
CodePudding user response:
You need to have a custom init(from:)
in Main
and decode a
to the right subclass directly
class Main: Codable {
let a: A
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if let object = try? container.decode(B1.self, forKey: .a) {
a = object
} else {
a = try container.decode(B2.self, forKey: .a)
}
}
}