Decoding an Array<Superclass> that contains a heterogenous mix of Superclass and Subclass: Superclass incorrectly infers all members to be of type Superclass, thereby breaking decoding as Subclass.init(from🙂 is never called.
Steps to Reproduce:
Assuming a class Superclass: Codable and class Subclass: Superclass, where Superclass has a var children: [Superclass], a round trip through an encoder/decoder of any type loses the subclass type information of any Subclass instances in the children array (they are all assumed to be Superclass). This breaks decoding of Subclass because the subclass' overridden init(from🙂 is never called since it's incorrectly assumed to be of type Superclass. Demonstration playground attached.
Expected Results:
When an encoded Array<Superclass> with heterogenous mix of Superclass and Subclass: Superclass is decoded, I expect the type information to be preserved so that instances of Subclass have their init(from🙂 called.
Observed Results:
In the attached simplified Playground example, the line:
let firstDecodedChild = decodedRoot.children.first as? Subclass
...evaluates to nil because the as? fails to cast what it assumes to be a Superclass instance to a Subclass. If you remove the as? you get an instance of Superclass.
In a more complex model with more properties, you get complaints about keys not being found, presumably because the Decoders are not descending into the Subclass instances' "super" containers as it thinks it has everything it needs.
The text was updated successfully, but these errors were encountered:
I'll copy the explanation from the Radar out to share publicly.
Unlike the existing NSCoding API (NSKeyedArchiver), the new Swift 4 Codable implementations do not write out type information about encoded types into generated archives, for both flexibility and security. As such, at decode time, the API can only use the concrete type your provide in order to decode the values (in your case, the superclass type).
Alternatively, if you know the possible subclasses which can be decoded from the payload, you can get an unkeyed container (instead of requesting Array<Superclass>) and attempt to decode instances of the subclasses out of the container; if those fail, you can decode the superclass successfully.
Thank you for this explanation. I'm not sure I agree with the approach if the goal is to be a Swiftlier serialization system than NSCoding but I see the value in it. What led me to discover this (I'll call it a) limitation is that I was converting a document-based application from NSCoding and couldn't understand why this was happening.
I feel that I'm missing something though, concerning your comment about getting an unkeyed container instead of Array<Superclass>. Could you elaborate a bit?
I think the responder (who wasn't me) was going for something like
let element: Superclass
do {
element = try container.decode(Subclass.self)
} catch let error {
element = try container.decode(Superclass.self)
}
But this would be trickier if you had multiple subclasses, and wouldn't work if the coded representation doesn't have enough information to distinguish subclasses and superclasses, of course.
swift-ci commentedJun 29, 2017
Attachment: Download
Environment
Swift 4, Xcode 9 (betas 1 and 2), macOS 10.13 (betas 1 and 2).
Additional Detail from JIRA
md5: cb8ce6d42a452adfc03b7acb6f106963
is duplicated by:
Issue Description:
Decoding an Array<Superclass> that contains a heterogenous mix of Superclass and Subclass: Superclass incorrectly infers all members to be of type Superclass, thereby breaking decoding as Subclass.init(from🙂 is never called.
Steps to Reproduce:🙂 is never called since it's incorrectly assumed to be of type Superclass. Demonstration playground attached.
Assuming a class Superclass: Codable and class Subclass: Superclass, where Superclass has a var children: [Superclass], a round trip through an encoder/decoder of any type loses the subclass type information of any Subclass instances in the children array (they are all assumed to be Superclass). This breaks decoding of Subclass because the subclass' overridden init(from
Expected Results:🙂 called.
When an encoded Array<Superclass> with heterogenous mix of Superclass and Subclass: Superclass is decoded, I expect the type information to be preserved so that instances of Subclass have their init(from
Observed Results:
In the attached simplified Playground example, the line:
let firstDecodedChild = decodedRoot.children.first as? Subclass
...evaluates to nil because the as? fails to cast what it assumes to be a Superclass instance to a Subclass. If you remove the as? you get an instance of Superclass.
In a more complex model with more properties, you get complaints about keys not being found, presumably because the Decoders are not descending into the Subclass instances' "super" containers as it thinks it has everything it needs.
The text was updated successfully, but these errors were encountered: