使用@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
相关产品推荐
相关产品推荐

