簡體   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