使用枚举类型作为字典键进行JSON解码失败的问题咨询
使用枚举类型作为字典键进行JSON解码失败的问题咨询
我最近在尝试用枚举类型作为字典的键,然后从JSON中加载数据时碰到了个头疼的问题——用String类型当字典键的时候,JSON解码完全没问题,但换成带String原始值的枚举类型后就直接失败了。想问问大家这是得加什么额外配置,还是这种场景本来就没法直接用?
先给大家看看我的测试代码:
import Foundation var jsonString = """ { "key": { "enumKey1": "value", "enumKey2": "value" } } """ struct UntypedObject: Codable { let key: [String: String] } struct TypedObject: Codable { let key: [EnumKey: String] } enum EnumKey: String, Codable { case enumKey1 case enumKey2 } do { let data = try Data(jsonString.utf8) let decodedUntyped = try JSONDecoder().decode(UntypedObject.self, from: data) let decodedTyped = try JSONDecoder().decode(TypedObject.self, from: data) } catch { print("Failed to decode json: \(error.localizedDescription)") }
解码UntypedObject一切正常,但解码TypedObject时就会抛出错误:
Failed to decode json: The data couldn't be read because it isn't in the correct format.
问题原因
其实这个坑在于:虽然你的枚举已经遵循了Codable和String协议,但JSONDecoder默认的字典键解码逻辑并不会自动把字符串键转换成枚举值——它只处理了普通的枚举值解码,没考虑字典键这种特殊场景。
两种解决思路
思路一:给TypedObject自定义解码逻辑
直接在TypedObject里手动实现init(from:)方法,先把字典解码成[String: String],再手动转换成枚举键的字典:
struct TypedObject: Codable { let key: [EnumKey: String] // 定义编码键 enum CodingKeys: String, CodingKey { case key } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) // 先解码成字符串键的字典 let stringKeyDict = try container.decode([String: String].self, forKey: .key) var enumKeyDict = [EnumKey: String]() for (rawKey, value) in stringKeyDict { // 把字符串转换成枚举值,遇到无效键直接抛错 guard let enumKey = EnumKey(rawValue: rawKey) else { throw DecodingError.dataCorruptedError( forKey: .key, in: container, debugDescription: "发现无效的字典键:\(rawKey)" ) } enumKeyDict[enumKey] = value } self.key = enumKeyDict } // 如果需要编码(把TypedObject转成JSON),还要实现encode方法 func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) // 把枚举键的字典转换成字符串键的字典再编码 let stringKeyDict = key.reduce(into: [String: String]()) { result, entry in result[entry.key.rawValue] = entry.value } try container.encode(stringKeyDict, forKey: .key) } }
思路二:扩展解码器,通用处理枚举键的字典
如果你的项目里有很多地方需要用枚举当字典键,可以扩展KeyedDecodingContainer,写一个通用的解码方法,这样所有类似的结构体都不用重复写解码逻辑:
extension KeyedDecodingContainer { // 通用方法:解码字符串键的字典并转换成枚举键的字典 func decode<T, U>(_ type: [T: U].Type, forKey key: Key) throws -> [T: U] where T: RawRepresentable, T.RawValue == String, U: Decodable { let stringDict = try decode([String: U].self, forKey: key) return stringDict.reduce(into: [T: U]()) { result, entry in guard let enumKey = T(rawValue: entry.key) else { // 这里可以根据需求选择抛错或者忽略无效键 fatalError("无效的枚举键:\(entry.key)") } result[enumKey] = entry.value } } }
这样TypedObject就可以保持最开始的简洁写法,不用自定义init(from:)了,解码器会自动调用我们扩展的方法来处理枚举键的转换。
试一下上面的任意一种方法,你就能成功解码TypedObject了~
内容来源于stack exchange
相关产品推荐
相关产品推荐

