使用@propertyWrapper实现Decodable时如何兼容可选缺失键?
问题原因
- Swift 自动合成
Decodable实现时,会将加了属性包装器的属性的属性包装器类型本身作为解码目标,而非它的wrappedValue类型 - 你的
SomeKindOfBool类型本身是非可选的,因此自动合成逻辑会强制校验对应键必须存在,找不到就抛出Swift.DecodingError.keyNotFound错误,和wrappedValue是否为可选没有关联
解决方案
有两种常用的实现方案,都可以满足键缺失时自动解码为nil的需求:
方案1:扩展KeyedDecodingContainer统一处理同类场景
这个方案不需要修改原有业务层的属性声明,只需要添加全局扩展适配所有带可选值的属性包装器解码场景:
// 先声明属性包装器约束协议 protocol PropertyWrapper { associatedtype WrappedValue init(wrappedValue: WrappedValue) } extension KeyedDecodingContainer { // 重载解码方法,针对wrappedValue为可选的属性包装器做特殊处理 func decode<T>(_ type: T.Type, forKey key: Key) throws -> T where T : Decodable, T: PropertyWrapper, T.WrappedValue: ExpressibleByNilLiteral { if let value = try decodeIfPresent(type, forKey: key) { return value } else { // 键不存在时直接返回wrappedValue为nil的包装器实例 return T(wrappedValue: nil) } } } // 给你的自定义属性包装器遵守该协议 extension SomeKindOfBool: PropertyWrapper { init(wrappedValue: Bool?) { self.wrappedValue = wrappedValue } }
方案2:直接给属性添加默认值
直接给加了属性包装器的属性添加默认值= nil,编译器会自动识别为键不存在时使用该默认值,无需强制校验键存在:
首先给属性包装器补充支持默认值的构造器:
@propertyWrapper struct SomeKindOfBool: Decodable { var wrappedValue: Bool? // 新增支持默认值的构造器 init(wrappedValue: Bool?) { self.wrappedValue = wrappedValue } init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() if let stringifiedValue = try? container.decode(String.self) { switch stringifiedValue.lowercased() { case "false": wrappedValue = false case "true": wrappedValue = true default: wrappedValue = nil } } else { wrappedValue = try? container.decode(Bool.self) } } }
然后修改MyType的属性声明,添加默认值:
public struct MyType: Decodable { @SomeKindOfBool var someKey: Bool? = nil }
两种方案修改完成后,你的原有测试代码都可以正常运行,输出结果为:
true false nil
内容的提问来源于stack exchange,提问作者Ortwin Gentz
相关产品推荐
相关产品推荐

