Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SR-5331] Swift 4 Decodable Loses Subclass Type Information #47905

Closed
swift-ci opened this issue Jun 29, 2017 · 3 comments
Closed

[SR-5331] Swift 4 Decodable Loses Subclass Type Information #47905

swift-ci opened this issue Jun 29, 2017 · 3 comments

Comments

@swift-ci
Copy link
Collaborator

swift-ci commented Jun 29, 2017

Previous ID SR-5331
Radar https://bugreport.apple.com/web/?problemID=32911973
Original Reporter jnozzi (JIRA User)
Type Bug
Status Resolved
Resolution Invalid

Attachment: Download

Environment

Swift 4, Xcode 9 (betas 1 and 2), macOS 10.13 (betas 1 and 2).

Additional Detail from JIRA
Votes 0
Component/s Standard Library
Labels Bug
Assignee None
Priority Medium

md5: cb8ce6d42a452adfc03b7acb6f106963

is duplicated by:

  • SR-5381 Codable with array of base class

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:
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.

@belkadan
Copy link
Contributor

belkadan commented Jun 29, 2017

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).

This is by design — if you need the dynamism required to do this, we recommend that you adopt NSSecureCoding and use NSKeyedArchiver/NSKeyedUnarchiver, which will allow you to decode based on the class found in the archive rather than what is requested at runtime. See https://developer.apple.com/documentation/foundation/nscoding and https://developer.apple.com/documentation/foundation/nssecurecoding for more info.

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.

@swift-ci
Copy link
Collaborator Author

swift-ci commented Jun 29, 2017

Comment by Joshua Nozzi (JIRA)

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?

@belkadan
Copy link
Contributor

belkadan commented Jun 29, 2017

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 swift-ci transferred this issue from apple/swift-issues Apr 25, 2022
This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants