[英]Swift Codable: Cannot decode dictionary of type [String: Any] or [String: Decodable]
In my custom initializer I'd like to decode a dictionary from JSON and then assign its values to properties in the class. To my surprise, compiler does not allow me to decode the dictionary, I get the error:在我的自定义初始化程序中,我想解码 JSON 中的字典,然后将其值分配给 class 中的属性。令我惊讶的是,编译器不允许我解码字典,我收到错误消息:
Value of protocol type 'Any' cannot conform to 'Decodable'; only struct/enum/class types can conform to protocols
If I try to decode dictionary of type [String: Decodable] the error message says:如果我尝试解码 [String: Decodable] 类型的字典,则错误消息显示:
Value of protocol type 'Decodable' cannot conform to 'Decodable'; only struct/enum/class types can conform to protocols
My initializer looks like this:我的初始化器看起来像这样:
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
}
...
}
When I looked for the answer I found this answer and according to it the code above should not cause any problems.当我寻找答案时,我找到了这个答案,根据它,上面的代码应该不会引起任何问题。
What you are looking for is nestedContainer
.您正在寻找的是nestedContainer
。 It helps you "skip" a hierarchy level in your JSON. Ie: let's imagine that in your code, it's all in the same level (one struct), but in the JSON, it's not, there is a sub dictionary.它可以帮助您“跳过”JSON 中的层次结构级别。即:假设在您的代码中,它们都在同一级别(一个结构)中,但在 JSON 中,它不是,有一个子字典。
For test purpose, your JSON might look like:出于测试目的,您的 JSON 可能如下所示:
{
"title": "normal",
"tracks": {
"name": "myName",
"total": 3
}
}
If we want in our model:如果我们想要在我们的 model 中:
struct TestStruct: Codable {
let title: String
let name: String
let total: Int
}
We need to use nestedContainer(keyedBy: forKey:)
:我们需要使用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.
}
}
In your sample, you used decodeIfPresent()
for the subdictionary.在您的示例中,您使用decodeIfPresent()
作为子词典。 It's unclear if it was for test purpose, or if the sub dictionary was sometimes not present.目前还不清楚它是出于测试目的,还是子词典有时不存在。 If that's the case and you can have a JSON like this:如果是这种情况,您可以像这样拥有 JSON:
{
"title": "normal"
}
Then, before calling nestedContainer(keyedBy: forKey:)
, use if container.contains(TopLevelKeys.tracks) {}
and set default values if needed in the else
case.然后,在调用nestedContainer(keyedBy: forKey:)
之前,使用if container.contains(TopLevelKeys.tracks) {}
并在else
情况下根据需要设置默认值。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.