Subclassing swift generic decodable type

允我心安 提交于 2019-12-11 04:06:08

问题


EDIT: As Rob Napier wrote, the problem exists in Xcode 9.2. In Xcode 9.3 the issue is no longer relevant.

My server json responses are all packed inside data object:

{ 
    "data": {...}
}

So I have the following generic type to parse JSON:

class DataContainer<T: Decodable>: Decodable {

    let data: T

    init(data: T)
        self.data = data
    }
}

Most of the time it works fine, but there is one response in which I also need to parse included field so I have created SpecificDataContainer subclass:

class SpecificDataContainer: DataContainer<DataObject> {
    let included: [IncludedObject]

    init() {
        included = []
        super.init(data: DataObject(id: ""))
    }
}

Implementation above gives me compiler error 'required' initializer 'init(from:)' must be provided by subclass of 'DataContainer'.

I implemented init(from:) in the SpecificDataContainer but compiler still gives me the same error.

It seems I miss something obvious here. What am I doing wrong? Here is my full code:

import Foundation

let jsonData = """
{
    "data": {
        "id": "some_id"
    },
    "included": [
        {
            "id": "some_id2"
        }
    ]
}
""".data(using:.utf8)!

struct DataObject: Decodable {
    let id: String
}

struct IncludedObject: Decodable {
    let id: String
}

class DataContainer<T: Decodable>: Decodable {
    let data: T

    init(data: T) {
        self.data = data
    }
}

class SpecificDataContainer: DataContainer<DataObject> {
    let included: [IncludedObject]

    init() {
        included = []
        super.init(data: DataObject(id: ""))
    }

    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        var includedArray = try container.nestedUnkeyedContainer(forKey: .included)

        var includedObjects:[IncludedObject] = []
        while !includedArray.isAtEnd {
            let includedObject = try includedArray.decode(IncludedObject.self)
            includedObjects.append(includedObject)
        }
        self.included = includedObjects

        try super.init(from: decoder)
    }

    private enum CodingKeys: String, CodingKey {
        case data = "data"
        case included = "included"
    }
}

let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
if let obj = try? decoder.decode(SpecificDataContainer.self, from: jsonData) {
    print("object id \(obj.data.id)")
} else {
    print("Fail!")
}

回答1:


For some reason Xcode doesn't recognise the auto-generated init(from:) in subclasses of Codable (as Rob said it might be a bug). Until Xcode 9.3 is out you can work around this issue by adding the initializer to the base class also:

class DataContainer<T: Decodable>: Decodable {

let data: T

enum CodingKeys: String, CodingKey {
    case data
}

required init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    data = try container.decode(T.self, forKey: .data)
}



回答2:


This appears to be a bug in Xcode 9.2. In 9.3b4 your code is fine.



来源:https://stackoverflow.com/questions/49540520/subclassing-swift-generic-decodable-type

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!