You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 14:57:57