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-5125] Encodable not decoding properties with subclasses #4091

Closed
swift-ci opened this issue Jun 6, 2017 · 2 comments
Closed

[SR-5125] Encodable not decoding properties with subclasses #4091

swift-ci opened this issue Jun 6, 2017 · 2 comments

Comments

@swift-ci
Copy link
Contributor

swift-ci commented Jun 6, 2017

Previous ID SR-5125
Radar None
Original Reporter Dimillian (JIRA User)
Type Bug
Status Closed
Resolution Duplicate
Environment

Xcode 9 / Swift 4 / Experimental build system

Additional Detail from JIRA
Votes 0
Component/s Foundation, Standard Library
Labels Bug
Assignee @itaiferber
Priority Medium

md5: 37774e4e19ebbc164774b2d906332e01

duplicates:

  • SR-4772 Classes conforming to Codable need to synthesize overrides instead of inheriting conformance

Issue Description:

In this code, the groceries properties is not deserialised from JSON. The name property is, but not the groceries one.

import Foundation

let json = """
{
"name": "wow",
"groceries": [
    {
        "name": "Banana",
        "points": 200,
        "description": "A banana grown in Ecuador."
    },
    {
        "name": "Orange",
        "points": 100,
        "description": "An orange."
    }
]
}
""".data(using: .utf8)!

class BaseStore: Codable {
    var name: String!
}

class Store: BaseStore {
    var groceries: [GroceryProduct]!
}

class GroceryProduct: Codable {
    var name: String!
    var points: Int!
    var description: String!
}

let decoder = JSONDecoder()
do {
    let products = try decoder.decode(Store.self, from: json)
    print("\(products.name)")
    print("\(products.groceries)")
} catch {
    print(error)
}

If you flatify the Store/BaseStore class, it actually works. Is it a bug or a limitation of Encodable?
So this code works:

import Foundation

let json = """
{
"name": "wow",
"groceries": [
    {
        "name": "Banana",
        "points": 200,
        "description": "A banana grown in Ecuador."
    },
    {
        "name": "Orange",
        "points": 100,
        "description": "An orange."
    }
]
}
""".data(using: .utf8)!

class BaseStore: Codable {
    var name: String!
}

class Store: Codable {
    var name: String!
    var groceries: [GroceryProduct]!
}

class GroceryProduct: Codable {
    var name: String!
    var points: Int!
    var description: String!
}

let decoder = JSONDecoder()
do {
    let products = try decoder.decode(Store.self, from: json)
    print("\(products.name)")
    print("\(products.groceries)")
} catch {
    print(error)
}
@swift-ci
Copy link
Contributor Author

swift-ci commented Jun 7, 2017

Comment by Thomas Ricouard (JIRA)

Thanks for the talk!

I want to add some detail into why I think this is important:
In the case of our REST API we've made a backend framework which handle automatic conversion of JSON response into Swift objects.
Previously we used reflection, and setValue so we had to inherited of NSObject.

With Codable and JSONDecoder we wants to do it in pure Swift.
Our REST API is pretty standard, our objects share different base properties across our endpoint, so we want to have a base class/struct which implement Codable and some standard properties (like an object id). And then have subclass if this for any object which is usable with our API.

I hope it make sense, and I get why it may be not the best be doable. But I think it would be good to think about this use case.

@itaiferber
Copy link
Contributor

As mentioned in person, there are a few issues here. The primary concern is SR-4772Store inherits its parent class's Codable conformance, so it only decodes the properties its parent looks for.
However, even when that is fixed, this type of behavior is not given by default. The default behavior is to do the safe thing: encapsulate parent data using a superEncoder and superDecoder. There is simply no way to know what type of container the parent class will ask for (e.g. Store is allowed to request a keyed encoder/decoder, but BaseStore can ask for an unkeyed encoder/decoder — those can't be shared!), so it's not safe for the compiler to do anything but use those encapsulating encoders.

The default result you'd get is

{
  "super": {"name": "wow"},
  "groceries": [
    {
      "name": "Banana",
       "points": 200,
       "description": "A banana grown in Ecuador."
    },
    {
      "name": "Orange",
      "points": 100,
      "description": "An orange."
    }
  ]
}

If you control the parent class and know for a fact that it's safe to pass your own encoder or decoder to them directly, you can do that in a custom encode(to🙂 and init(from🙂:

public func encode(to encoder: Encoder) throws {
    // Ask for a container and encode into it.
    super.encode(to: encoder)
}

@swift-ci swift-ci transferred this issue from apple/swift-issues Apr 25, 2022
@shahmishal shahmishal transferred this issue from apple/swift May 5, 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