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

使用@propertyWrapper后JSONDecoder在属性缺失时解码失败

解决@propertyWrapper属性在JSON键缺失时的解码失败问题

我有一个支持自定义JSON反序列化的数据模型类PersonInfo,其中属性flexibleProp在JSON中可以是String?或Int?类型,但在模型中需要始终保持为Int?。为此我实现了@propertyWrapper结构体FlexibleInt来处理这个自定义反序列化逻辑。多数场景下解码正常,但当JSON中完全缺失flexibleProp键时,解码会失败,而普通可选属性regularProp缺失时会自动设为nil,无解码问题。

原实现代码

class PersonInfo: Codable {
    var regularProp: String?
    @FlexibleInt var flexibleProp: Int?
    
    init() {
    }
}

@propertyWrapper struct FlexibleInt: Codable {
    var wrappedValue: Int?
    
    init() {
        self.wrappedValue = nil
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        if let value = try? container.decode(String.self) {
            wrappedValue = Int(value)
        } else if let intValue = try? container.decode(Int.self) {
            wrappedValue = intValue
        } else {
            wrappedValue = nil
        }
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        try container.encode(wrappedValue)
    }
}

let decoder = JSONDecoder()
let payload1 = "{ \"flexibleProp\": \"123888\", \"regularProp\": \"qqq\" }"
let person1 = try decoder.decode(PersonInfo.self, from: payload1.data(using: .utf8)!)
let payload2 = "{ \"flexibleProp\": \"\" }"
let person2 = try decoder.decode(PersonInfo.self, from: payload2.data(using: .utf8)!)
let payload3 = "{ \"flexibleProp\": \"sss\" }"
let person3 = try decoder.decode(PersonInfo.self, from: payload3.data(using: .utf8)!)
let payload4 = "{ }"
let person4 = try decoder.decode(PersonInfo.self, from: payload4.data(using: .utf8)!)  // FAILS HERE <------------

解码错误信息

▿ DecodingError
  ▿ keyNotFound : 2 elements
    - .0 : CodingKeys(stringValue: "flexibleProp", intValue: nil)
    ▿ .1 : Context
      - codingPath : 0 elements
      - debugDescription : "No value associated with key CodingKeys(stringValue: \"flexibleProp\", intValue: nil) (\"flexibleProp\")."
      - underlyingError : nil

问题原因

默认情况下,Swift编译器为带@propertyWrapper的属性生成Codable代码时,会将其视为非可选类型,即使包装器的wrappedValue是可选的。因此当JSON中缺失对应键时,解码器会抛出keyNotFound错误,而普通可选属性则会自动设为nil。

解决方法

方法一:让@propertyWrapper支持nil字面量(推荐)

让FlexibleInt结构体符合ExpressibleByNilLiteral协议,让编译器识别它可以被初始化为nil,从而将对应的属性视为可选类型,自动处理键缺失的情况:

@propertyWrapper struct FlexibleInt: Codable, ExpressibleByNilLiteral {
    var wrappedValue: Int?
    
    init() {
        self.wrappedValue = nil
    }
    
    // 实现ExpressibleByNilLiteral协议的初始化方法
    init(nilLiteral: ()) {
        self.init()
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        if let value = try? container.decode(String.self) {
            wrappedValue = Int(value)
        } else if let intValue = try? container.decode(Int.self) {
            wrappedValue = intValue
        } else {
            wrappedValue = nil
        }
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        try container.encode(wrappedValue)
    }
}

修改后PersonInfo类无需任何改动,当JSON中缺失flexibleProp键时,解码器会自动使用nilLiteral初始化FlexibleInt,将flexibleProp设为nil,解码成功。

方法二:手动实现PersonInfo的Codable协议

如果不想修改FlexibleInt,也可以手动实现PersonInfo的init(from:)和encode(to:)方法,显式处理flexibleProp的缺失情况:

class PersonInfo: Codable {
    var regularProp: String?
    @FlexibleInt var flexibleProp: Int?
    
    init() {
    }
    
    enum CodingKeys: String, CodingKey {
        case regularProp, flexibleProp
    }
    
    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        // 处理普通可选属性
        regularProp = try container.decodeIfPresent(String.self, forKey: .regularProp)
        // 用decodeIfPresent处理包装器属性,键缺失时返回nil
        let wrapper = try container.decodeIfPresent(FlexibleInt.self, forKey: .flexibleProp)
        flexibleProp = wrapper?.wrappedValue
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encodeIfPresent(regularProp, forKey: .regularProp)
        // 仅当wrappedValue不为nil时编码
        if let value = flexibleProp {
            let wrapper = FlexibleInt()
            wrapper.wrappedValue = value
            try container.encode(wrapper, forKey: .flexibleProp)
        }
    }
}

内容的提问来源于stack exchange,提问作者Mando

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 14:54:55