简体   繁体   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?

Little stumped by something that's best illustrated with a class...有点被课堂上最好的说明的东西难住了......

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?
}

The problem is the line assigning the temp concreteObject complains with the following...问题是分配临时concreteObject的行抱怨以下...

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

This is of course because the type returned from the dictionary is Decodable.Type and not something like 'String.self' thus it's not sure which decode overload to use.这当然是因为从字典返回的类型是Decodable.Type而不是像 'String.self' 这样的东西,因此不确定要使用哪个decode重载。

So if you have the concrete type stored in a variable of Any.Type , how can you pass that to the correct decode overload?因此,如果您将具体类型存储在Any.Type变量中,您如何将其传递给正确的解码重载?

KeyedDecodingContainer.decode(_:forKey:) (and the other containers' decode(...) methods) is a generic method, taking the type parameter as a generic input. KeyedDecodingContainer.decode(_:forKey:) (以及其他容器的decode(...)方法)是一个通用方法,将类型参数作为通用输入。 In order to call the method, then, Swift needs to know the generic type statically, at runtime.为了调用该方法,Swift 需要在运行时静态地知道泛型类型。 Although you have a value of Decodable.Type , in order to dispatch the method call, Swift would need the specific type at compile-time.尽管您有一个Decodable.Type值, Decodable.Type为了调度方法调用,Swift 在编译时需要特定类型。

As mentioned in a comment, the simplest change you can make to the above code is to lower your generalization down into specific decode<T>(_:forKey:) calls:正如评论中提到的,您可以对上述代码进行的最简单更改是将泛化降低到特定的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
}

This specifies to the compiler the generic type to dispatch the call.这向编译器指定了要调度调用的泛型类型。 Depending on your specific needs there are other solutions out there, but this is the gist of what you'd eventually need to do.根据您的特定需求,还有其他解决方案,但这是您最终需要做的事情的要点。 (You can indeed wrap the calls in closures to add a layer of indirection.) (您确实可以将调用包装在闭包中以添加一个间接层。)


This is assuming that you have to match JSON which specifies the type inline (eg { "type": "int", "value": 42 } ), but if you have control over the JSON data, you can likely avoid this by creating, say, an enum that represents all possible types you expect (as laid out in How to deal with completely dynamic JSON responses ):这是假设您必须匹配指定内联类型的 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))
            }
        }
    }
}

Instead of looking for a type selector in the decoded data, you can attempt to decode as the types you need, and stick with what succeeds.您可以尝试解码为您需要的类型,而不是在解码数据中寻找类型选择器,并坚持使用成功的类型。 This assumes that types don't overlap (eg might be decodable as one another, like Int8 and Int , in which case you'd need to be very careful about the order you decode in).这假设类型不重叠(例如,可能可以相互解码,如Int8Int ,在这种情况下,您需要非常小心解码的顺序)。

You could always use a giant switch statement checking every possible value (ew) but if you were going to do that the much better way would be to use type erasure.. the downside is you pretty much have to go through all your possible classes and declare them as conforming to, or implement a func that returns the decodable type - which can be cumbersome.你总是可以使用一个巨大的 switch 语句来检查每个可能的值(ew),但如果你打算这样做,更好的方法是使用类型擦除......缺点是你几乎必须经历所有可能的类和将它们声明为符合或实现返回可解码类型的 func - 这可能很麻烦。

Ideally we could just get "out of the box" support for anything that's Decodable .理想情况下,我们可以为任何可Decodable东西提供“开箱即用”的支持。

To do this we would require a language feature called opening existentials - there's a great and detailed write up of the issues you're facing in this question ( Protocol doesn't conform to itself? )要做到这一点,我们需要一个称为开放存在主义的语言功能 - 您在这个问题中面临的问题有一个很好的详细说明( 协议不符合自身?

Anyways, no clue if this works but I WAS able to get something to compile that seems to do what you want:无论如何,不​​知道这是否有效,但我能够得到一些似乎可以做你想做的事情:

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

Explanation:解释:

The issue is that the compiler can't figure out which of the 16 decode: methods to use on KeyedDecodingContainer .问题是编译器无法确定要在KeyedDecodingContainer上使用的 16 种decode:方法中的KeyedDecodingContainer

Because swift lacks opening existentials it can't tell that passing an argument of Decodable.Type is the same as T.Type where T: Decodable因为 swift 缺少开放的存在性,所以它无法判断传递Decodable.Type的参数与Decodable.Type相同, T.Type where T: Decodable

So the first thing I did was work with Decodable directly instead of Decodable.Type .所以我做的第一件事是直接使用Decodable而不是Decodable.Type That meant changing both the decodableTypesLookup to take be [String: Decodable] and creating my own decoding method on KeyedDecodingContainer that took in a Decodable instead of a meta type (like the standard decode: func takes).这意味着改变两个decodableTypesLookup采取是[String: Decodable]和创建我自己的解码方法KeyedDecodingContainer是参加了一个Decodable ,而不是一元型(如标准decode: FUNC需要)。

The line that does worry me here is:这里确实让我担心的那一行是:

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

If that doesn't way perhaps there's a way to cast to a generic type instead of using as!如果那不行,也许有一种方法可以转换为泛型类型而不是使用as! ? ?

Anyway, that left me with an error of (Generic parameter T could not be inferred) on无论如何,这给我留下了错误(无法推断通用参数 T)

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

and that's just because you can't have an actual concrete Decodable() and it has no idea what the concrete object should be.那只是因为你不能有一个实际的具体Decodable()并且它不知道具体对象应该是什么。

To get around that I just created a throw-away:为了解决这个问题,我刚刚创建了一个扔掉的东西:

private struct DecodableObject: Decodable {}

Just so I can tell the compiler that should be the concrete object:只是为了告诉编译器应该是具体的对象:

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

we can then obviously covert that to the expected type Any?然后我们显然可以将其转换为预期的类型Any? (perhaps AnyObject would be better tbh) (也许AnyObject会更好 tbh)

Hope it works..希望它有效..

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

相关问题 mapstructure 如何使用鉴别器来解码具体类型 - How can mapstructure use a discriminator to decode concrete type 如何在Elm 0.17中对具体类型进行http请求和解码响应? - How to make http request and decode response to concrete type in Elm 0.17? 您能否将子 JSON 对象解码为其字符串表示而不是具体类型? - Can you decode a child JSON object into its string representation instead of a concrete type? 获取案例类的具体类型 - Get concrete type of a case class 如何在 Swift [45] 可解码协议中解码 JSON 字典类型的属性 - How to decode a property with type of JSON dictionary in Swift [45] decodable protocol 杰克逊基于类型反序列化为具体类 - Jackson Deserialize To Concrete Class Based On Type 反序列化为map [string] interface {}作为具体的地图类型 - Deserialize into map[string]interface{} as a concrete map type 如何指示Json.NET将接口类型转换为具体类型而无需访问序列化程序? - How can I instruct Json.NET to convert an interface type to a concrete type without access to the serializer? Swift 4 JSON Decodable 解码类型变化的最简单方法 - Swift 4 JSON Decodable simplest way to decode type change 如何在没有密钥的 Swift 5 可解码协议中解码字典数组类型的属性? - How to decode a property with type of Array of dictionary in Swift 5 decodable protocol without key?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM