繁体   English   中英

如果该具体类型在运行时存储在 'Decodable.Type' 变量中,您如何解码已知的具体类型?

[英]How do you decode a known, concrete type if that concrete type is stored in a variable of 'Decodable.Type' at runtime?

有点被课堂上最好的说明的东西难住了......

class AnyDecodableWrapper : Decodable {

    static let decodableTypesLookup:[String:Decodable.Type] = [ <-- 'Decodable.Type' here is what's causing the problem
        "str": String.self,
        "int": Int.self,
        "foo": Foo.self
    ]

    enum CodingKeys : String, CodingKey {
        case typeKey
        case value
    }

    required init(from decoder: Decoder) throws {

        // Get the container for the CodingKeys
        let container = try decoder.container(keyedBy: CodingKeys.self)

        // Get the key to look up the concrete type
        typeKey = try container.decode(String.self, forKey:.typeKey)

        // Attempt to get the concrete type from the key
        guard let concreteType = AnyDecodableWrapper.decodableTypesLookup[typeKey] else {
            value = nil
            return
        }

        // Attempt to decode an instance of the concrete type
        let concreteObject = try container.decode(concreteType, forKey: .value)

        value = concreteObject
    }

    let typeKey : String
    let value   : Any?
}

问题是分配临时concreteObject的行抱怨以下...

对成员 'decode(_:forKey:)' 的不明确引用

这当然是因为从字典返回的类型是Decodable.Type而不是像 'String.self' 这样的东西,因此不确定要使用哪个decode重载。

因此,如果您将具体类型存储在Any.Type变量中,您如何将其传递给正确的解码重载?

KeyedDecodingContainer.decode(_:forKey:) (以及其他容器的decode(...)方法)是一个通用方法,将类型参数作为通用输入。 为了调用该方法,Swift 需要在运行时静态地知道泛型类型。 尽管您有一个Decodable.Type值, Decodable.Type为了调度方法调用,Swift 在编译时需要特定类型。

正如评论中提到的,您可以对上述代码进行的最简单更改是将泛化降低到特定的decode<T>(_:forKey:)调用:

switch typeKey {
case "int": value = try container.decode(Int.self,    forKey: .value)
case "str": value = try container.decode(String.self, forKey: .value)
case "foo": value = try container.decode(Foo.self,    forKey: .value)
default:    value = nil
}

这向编译器指定了要调度调用的泛型类型。 根据您的特定需求,还有其他解决方案,但这是您最终需要做的事情的要点。 (您确实可以将调用包装在闭包中以添加一个间接层。)


这是假设您必须匹配指定内联类型的 JSON(例如{ "type": "int", "value": 42 } ),但如果您可以控制 JSON 数据,则可以通过创建来避免这种情况,比如说,一个代表您期望的所有可能类型的enum (如如何处理完全动态的 JSON 响应中所述):

enum IntOrStringOrFoo: Decodable {
    case int(Int)
    case string(String)
    case foo(Foo)

    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        do {
            self = .int(try container.decode(Int.self))
        } catch DecodingError.typeMismatch {
            do {
                self = .string(try container.decode(String.self))
            } catch DecodingError.typeMismatch {
                self = .foo(try container.decode(Foo.self))
            }
        }
    }
}

您可以尝试解码为您需要的类型,而不是在解码数据中寻找类型选择器,并坚持使用成功的类型。 这假设类型不重叠(例如,可能可以相互解码,如Int8Int ,在这种情况下,您需要非常小心解码的顺序)。

你总是可以使用一个巨大的 switch 语句来检查每个可能的值(ew),但如果你打算这样做,更好的方法是使用类型擦除......缺点是你几乎必须经历所有可能的类和将它们声明为符合或实现返回可解码类型的 func - 这可能很麻烦。

理想情况下,我们可以为任何可Decodable东西提供“开箱即用”的支持。

要做到这一点,我们需要一个称为开放存在主义的语言功能 - 您在这个问题中面临的问题有一个很好的详细说明( 协议不符合自身?

无论如何,不​​知道这是否有效,但我能够得到一些似乎可以做你想做的事情:

class AnyDecodableWrapper : Decodable {

    static let decodableTypesLookup: [String: Decodable] = [
        "str": "",
        "int": 0
    ]

    enum CodingKeys : String, CodingKey {
        case typeKey
        case value
    }

    private struct DecodableObject: Decodable {}

    required init(from decoder: Decoder) throws {

        // Get the container for the CodingKeys
        let container = try decoder.container(keyedBy: CodingKeys.self)

        typeKey = try container.decode(String.self, forKey:.typeKey)

        guard let concreteType = AnyDecodableWrapper.decodableTypesLookup[typeKey] else {
            value = nil
            return
        }

        let concreteObject: DecodableObject = try container.unambiguousDecode(concreteType, forKey: .value)
        value = concreteObject
    }

    let typeKey : String
    let value   : Any?
}

extension KeyedDecodingContainer {
    func unambiguousDecode<T: Decodable>(_ decodableType: Decodable, forKey key: KeyedDecodingContainer<K>.Key) throws -> T {
        let decodable = type(of: decodableType) as! T.Type
        return try decode(decodable, forKey: key)
    }
}

解释:

问题是编译器无法确定要在KeyedDecodingContainer上使用的 16 种decode:方法中的KeyedDecodingContainer

因为 swift 缺少开放的存在性,所以它无法判断传递Decodable.Type的参数与Decodable.Type相同, T.Type where T: Decodable

所以我做的第一件事是直接使用Decodable而不是Decodable.Type 这意味着改变两个decodableTypesLookup采取是[String: Decodable]和创建我自己的解码方法KeyedDecodingContainer是参加了一个Decodable ,而不是一元型(如标准decode: FUNC需要)。

这里确实让我担心的那一行是:

let decodable = type(of: decodableType) as! T.Type

如果那不行,也许有一种方法可以转换为泛型类型而不是使用as! ?

无论如何,这给我留下了错误(无法推断通用参数 T)

let concreteObject = try container.unambiguousDecode(concreteType, forKey: .value)

那只是因为你不能有一个实际的具体Decodable()并且它不知道具体对象应该是什么。

为了解决这个问题,我刚刚创建了一个扔掉的东西:

private struct DecodableObject: Decodable {}

只是为了告诉编译器应该是具体的对象:

let concreteObject: DecodableObject = try container.unambiguousDecode(concreteType, forKey: .value)

然后我们显然可以将其转换为预期的类型Any? (也许AnyObject会更好 tbh)

希望它有效..

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM