问题是Dictionary
's Codable
conformance目前只能正确处理String
和Int
个键.对于任何其他Key
类型(其中Key
是Encodable
/Decodable
)的字典,它是用一个unkeyed容器(JSON数组)编码和解码的,该容器具有交替的键值.
因此,在try 解码JSON时:
{"dictionary": {"enumValue": "someString"}}
在AStruct
中,"dictionary"
键的值应该是一个array.
所以
let jsonDict = ["dictionary": ["enumValue", "someString"]]
会起作用,产生JSON:
{"dictionary": ["enumValue", "someString"]}
然后会被解码成:
AStruct(dictionary: [AnEnum.enumValue: "someString"])
然而,我真的认为Dictionary
的Codable
一致性should能够正确地处理任何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")))