KeyedDecodingContainerProtocol.superDecoder(forKey:)与文档描述不符问题
Codable中superDecoder(forKey:)处理null值的问题分析
问题背景
按照superDecoder(forKey:)的文档描述,当对应key的条目为null时,该方法应抛出DecodingError.valueNotFound。但在解码第三个componentCategoryIcon为null的ComponentCategory实例时,代码并未进入预期的else分支,反而得到了一个看似有效的nestedDecoder,进而在Image初始化时抛出异常。
后端返回JSON
[ { "id":8, "title":"Title 1", "componentCategoryIcon":{ "id":34, "name":"icon_name_1", "type":"IMAGE" } }, { "id":8, "title":"Title 2", "componentCategoryIcon":{ "id":35, "name":"icon_name_2", "type":"IMAGE" } }, { "id":8, "title":"Title 3", "componentCategoryIcon": null } ]
自定义Codable结构体
struct ComponentCategory: Codable { let id: Int let title: String let componentCategoryIcon: Image? init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) let id = try container.decode(Int.self, forKey: .id) self.id = id self.title = try container.decode(String.self, forKey: .title) let savedComponentCategory = (decoder.userInfo[.savedComponentCategories] as? [ComponentCategory])?.first(where: { $0.id == id }) // 问题所在的判断逻辑 if let nestedDecoder = try? container.superDecoder(forKey: .componentCategoryIcon) { self.componentCategoryIcon = try Image(from: nestedDecoder, savedImage: savedComponentCategory?.componentCategoryIcon) } else { self.componentCategoryIcon = nil } } }
struct Image: Codable { let id: Int let name: String let type: String? let path: String? let downloaded: Bool required init(from decoder: Decoder, savedImage: Image?) throws { let container = try decoder.container(keyedBy: CodingKeys.self) self.id = try container.decode(Int.self, forKey: .id) self.name = try container.decode(String.self, forKey: .name) self.type = try container.decodeIfPresent(String.self, forKey: .type) self.path = try container.decodeIfPresent(String.self, forKey: .path) self.downloaded = try container.decodeIfPresent(Bool.self, forKey: .downloaded) ?? savedImage?.downloaded ?? false } }
官方文档说明(翻译)
抛出
若给定key对应的条目为null,抛出DecodingError.valueNotFound。
问题分析
并非文档错误,而是对superDecoder(forKey:)的行为存在误解:
- 当对应key的条目为null时,
superDecoder(forKey:)不会直接抛出DecodingError.valueNotFound,而是会创建一个指向null值的解码器实例。 - 此时
try?会成功获取到这个解码器,但后续用它初始化Image时,由于Image的初始化需要解码id、name等非可选字段,而null值无法提供这些数据,才会抛出解码异常。
解决方案
需要先判断对应key的值是否为非null,再决定是否创建解码器进行Image的解码。修改ComponentCategory的init(from:)逻辑:
init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) let id = try container.decode(Int.self, forKey: .id) self.id = id self.title = try container.decode(String.self, forKey: .title) let savedComponentCategory = (decoder.userInfo[.savedComponentCategories] as? [ComponentCategory])?.first(where: { $0.id == id }) // 先判断值是否为非null if let _ = try container.decodeIfPresent([String: Any].self, forKey: .componentCategoryIcon) { let nestedDecoder = try container.superDecoder(forKey: .componentCategoryIcon) self.componentCategoryIcon = try Image(from: nestedDecoder, savedImage: savedComponentCategory?.componentCategoryIcon) } else { self.componentCategoryIcon = nil } }
或者更严谨地判断是否为null:
init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) let id = try container.decode(Int.self, forKey: .id) self.id = id self.title = try container.decode(String.self, forKey: .title) let savedComponentCategory = (decoder.userInfo[.savedComponentCategories] as? [ComponentCategory])?.first(where: { $0.id == id }) // 检查是否为null let isIconNull = try container.decodeIfPresent(NSNull.self, forKey: .componentCategoryIcon) != nil if !isIconNull { let nestedDecoder = try container.superDecoder(forKey: .componentCategoryIcon) self.componentCategoryIcon = try Image(from: nestedDecoder, savedImage: savedComponentCategory?.componentCategoryIcon) } else { self.componentCategoryIcon = nil } }
总结
superDecoder(forKey:)的文档描述存在歧义,实际行为是即使对应值为null也会返回解码器,而非直接抛出错误。正确的处理方式是先判断值的状态,再执行后续解码逻辑。
内容的提问来源于stack exchange,提问作者Giorgio
相关产品推荐
相关产品推荐

