问题是Dictionary's Codable conformance 目前只能正确处理String 和Int 键。对于具有任何其他 Key 类型的字典(其中 Key 是 Encodable/Decodable),它使用具有交替键值的 unkeyed 容器(JSON 数组)进行编码和解码.
因此在尝试解码 JSON 时:
{"dictionary": {"enumValue": "someString"}}
进入AStruct,"dictionary" 键的值应该是一个数组。
所以,
let jsonDict = ["dictionary": ["enumValue", "someString"]]
会工作,产生 JSON:
{"dictionary": ["enumValue", "someString"]}
然后将其解码为:
AStruct(dictionary: [AnEnum.enumValue: "someString"])
但是,我真的认为Dictionary 的Codable 一致性应该能够正确处理任何符合CodingKey 的类型作为其Key(其中AnEnum 可以) – 因为它可以使用该密钥对密钥容器进行编码和解码(请随时 file a bug 请求)。
在实现之前(如果有的话),我们总是可以构建一个包装器类型来执行此操作:
struct CodableDictionary<Key : Hashable, Value : Codable> : Codable where Key : CodingKey {
let decoded: [Key: Value]
init(_ decoded: [Key: Value]) {
self.decoded = decoded
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: Key.self)
decoded = Dictionary(uniqueKeysWithValues:
try container.allKeys.lazy.map {
(key: $0, value: try container.decode(Value.self, forKey: $0))
}
)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: Key.self)
for (key, value) in decoded {
try container.encode(value, forKey: key)
}
}
}
然后像这样实现:
enum AnEnum : String, CodingKey {
case enumValue
}
struct AStruct: Codable {
let dictionary: [AnEnum: String]
private enum CodingKeys : CodingKey {
case dictionary
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
dictionary = try container.decode(CodableDictionary.self, forKey: .dictionary).decoded
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(CodableDictionary(dictionary), forKey: .dictionary)
}
}
(或者只拥有CodableDictionary<AnEnum, String> 类型的dictionary 属性并使用自动生成的Codable 一致性——那么就用dictionary.decoded 来说吧)
现在我们可以按预期解码嵌套的 JSON 对象了:
let data = """
{"dictionary": {"enumValue": "someString"}}
""".data(using: .utf8)!
let decoder = JSONDecoder()
do {
let result = try decoder.decode(AStruct.self, from: data)
print(result)
} catch {
print(error)
}
// AStruct(dictionary: [AnEnum.enumValue: "someString"])
尽管说了这么多,但可以说,您使用带有 enum 作为键的字典所实现的只是带有可选属性的 struct(如果您希望给定的值始终是那里;使它成为非可选的)。
因此,您可能只希望您的模型看起来像:
struct BStruct : Codable {
var enumValue: String?
}
struct AStruct: Codable {
private enum CodingKeys : String, CodingKey {
case bStruct = "dictionary"
}
let bStruct: BStruct
}
这对你当前的 JSON 工作得很好:
let data = """
{"dictionary": {"enumValue": "someString"}}
""".data(using: .utf8)!
let decoder = JSONDecoder()
do {
let result = try decoder.decode(AStruct.self, from: data)
print(result)
} catch {
print(error)
}
// AStruct(bStruct: BStruct(enumValue: Optional("someString")))