Swift Codable:无法解码类型为[String:Any]或[String:Decodable]的字典

问题描述

在我的自定义初始化程序中,我想从JSON解码字典,然后将其值分配给类中的属性。令我惊讶的是,编译器不允许我解码字典,但出现错误

Value of protocol type 'Any' cannot conform to 'Decodable'; only struct/enum/class types can conform to protocols

如果我尝试对[String:Decodable]类型的字典进行解码,则错误消息会显示

Value of protocol type 'Decodable' cannot conform to 'Decodable'; only struct/enum/class types can conform to protocols

我的初始化程序如下:

public let total: Int

required init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    ...
    if let dict = try container.decodeIfPresent([String: Any].self,forKey: .tracks),let value = dict["total"] as? Int { // Error is displayed at this line
        total = value
    } else {
        total = 0
    }
    ...
}

当我寻找答案时,我发现了this answer,根据它,上面的代码应该不会引起任何问题。

解决方法

您要寻找的是nestedContainer。它可以帮助您“跳过” JSON中的层次结构级别。即:让我们想象一下,在您的代码中,它们全都处于同一级别(一个结构),但是在JSON中,它不是一个子词典。

出于测试目的,您的JSON可能类似于:

{
    "title": "normal","tracks": {
                   "name": "myName","total": 3
              }
}

如果要在模型中使用

struct TestStruct: Codable {
    let title: String
    let name: String
    let total: Int
}

我们需要使用nestedContainer(keyedBy: forKey:)

extension TestStruct {
    enum TopLevelKeys: String,CodingKey {
        case title
        case tracks
    }
    
    enum NestedLevelCodingKeys: String,CodingKey {
        case name
        case total
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: TopLevelKeys.self)
        self.title = try container.decode(String.self,forKey: .title)
        let subcontainer = try container.nestedContainer(keyedBy: NestedLevelCodingKeys.self,forKey: TopLevelKeys.tracks)
        self.name = try subcontainer.decode(String.self,forKey: .name)
        self.total = try subcontainer.decode(Int.self,forKey: .total) //You can use here a `decodeIfPresent()` if needed,use default values,etc.

    }
}

在示例中,您将decodeIfPresent()用于该子词典。目前尚不清楚它是否用于测试目的,或者子词典有时不存在。 如果是这样,您可以使用类似以下的JSON:

{
    "title": "normal"
} 

然后,在调用nestedContainer(keyedBy: forKey:)之前,请使用if container.contains(TopLevelKeys.tracks) {}并在else情况下根据需要设置默认值。