Better Codable through Property Wrappers
通过属性包装器提升你的 Codable
结构体。这些属性包装器的目标是避免实现自定义 init(from decoder: Decoder) throws
和忍受样板代码。
@LossyArray
@LossyArray
解码 Array 并过滤无效值,如果解码器无法解码值。这在 Array 包含非可选类型且您的 API 提供了 null 或在容器内无法解码的元素时很有用。
使用示例
轻松过滤原始容器中的 null 值
struct Response: Codable {
@LossyArray var values: [Int]
}
let json = #"{ "values": [1, 2, null, 4, 5, null] }"#.data(using: .utf8)!
let result = try JSONDecoder().decode(Response.self, from: json)
print(result) // [1, 2, 4, 5]
或静默排除失败实体
struct Failable: Codable {
let value: String
}
struct Response: Codable {
@LossyArray var values: [Failable]
}
let json = #"{ "values": [{"value": 4}, {"value": "fish"}] }"#.data(using: .utf8)!
let result = try JSONDecoder().decode(Response.self, from: json)
print(result) // [Failable(value: "fish")]
@LossyDictionary
@LossyDictionary
解码字典并在解码器无法解码值时过滤无效的键值对。如果字典打算包含非可选值,且您的 API 提供了 null 或在容器内无法解码的值,这非常有用。
使用
轻松过滤原始容器中的 null 值
struct Response: Codable {
@LossyDictionary var values: [String: String]
}
let json = #"{ "values": {"a": "A", "b": "B", "c": null } }"#.data(using: .utf8)!
let result = try JSONDecoder().decode(Response.self, from: json)
print(result) // ["a": "A", "b": "B"]
或静默排除失败实体
struct Failable: Codable {
let value: String
}
struct Response: Codable {
@LossyDictionary var values: [String: Failable]
}
let json = #"{ "values": {"a": {"value": "A"}, "b": {"value": 2}} }"#.data(using: .utf8)!
let result = try JSONDecoder().decode(Response.self, from: json)
print(result) // ["a": "A"]
@DefaultCodable
@DefaultCodable
提供了一个通用的属性包装器,允许使用自定义的 DefaultCodableStrategy
来设置默认值。这样可以在不牺牲属性包装器功能的同时,实现对缺失数据的自定义默认行为。以下是一些常用的默认策略,但它们同样可以作为实现自定义属性包装器的模板,以满足特定场景的需求。
虽然该策略在源代码中未提供,但创建适用于自定义数据流的默认策略是一件轻而易举的事情。
struct RefreshDaily: DefaultCodableStrategy {
static var defaultValue: CacheInterval { return CacheInterval.daily }
}
struct Cache: Codable {
@DefaultCodable<RefreshDaily> var refreshInterval: CacheInterval
}
let json = #"{ "refreshInterval": null }"#.data(using: .utf8)!
let result = try JSONDecoder().decode(Cache.self, from: json)
print(result) // Cache(refreshInterval: .daily)
@DefaultFalse
可选布尔类型很奇怪。曾经只表征“真或假”的类型,现在有三种可能的状态:.some(true)
、.some(false)
或 .none
。而 .none
状态可能会因为错误的决策而发生错误,误表示为“真”。
@DefaultFalse
通过默认将解码后的布尔值设为 false,以减轻这种混淆。如果解码器无法解码值,无论是遇到 null 还是某些意外的类型,都会设置默认值。
使用
struct UserPrivilege: Codable {
@DefaultFalse var isAdmin: Bool
}
let json = #"{ "isAdmin": null }"#.data(using: .utf8)!
let result = try JSONDecoder().decode(Response.self, from: json)
print(result) // UserPrivilege(isAdmin: false)
@DefaultEmptyArray
可选布尔类型的奇怪之处还体现在其他类型,如数组。Soroush 撰写了一篇优秀的博客文章,解释了为什么你可能想避免使用可选数组。不幸的是,Swift 中并没有直接提供这种功能。必须自己实现一个自定义的初始化器,以将 nil 合并到空数组中,这是一件相当麻烦的事情。
@DefaultEmptyArray
在解码器无法解码容器时将数组解码为空数组,而不是 nil。
用法
struct Response: Codable {
@DefaultEmptyArray var favorites: [Favorite]
}
let json = #"{ "favorites": null }"#.data(using: .utf8)!
let result = try JSONDecoder().decode(Response.self, from: json)
print(result) // Response(favorites: [])
@DefaultEmptyDictionary
如前所述,可选字典是另一个 nil 和空值相冲突的容器。
@DefaultEmptyDictionary
在解码器无法解码容器时将字典解码为空字典,而不是 nil。
用法
struct Response: Codable {
@DefaultEmptyDictionary var scores: [String: Int]
}
let json = #"{ "scores": null }"#.data(using: .utf8)!
let result = try JSONDecoder().decode(Response.self, from: json)
print(result) // Response(values: [:])
@LosslessValue
归功于 Ian Keen。
有时 API 可能是不可预测的。它们可能将某些形式的标识符或 SKU 在一个响应中作为 Int
处理,而在另一个响应中作为 String
。或者你现在可能会遇到当你期望布尔值时出现 "true"。这正是 @LosslessValue
发挥作用的时候。
@LosslessValue
会尝试将值解码为期望的类型,保留那些否则会引发异常或完全丢失的数据。
用法
struct Response: Codable {
@LosslessValue var sku: String
@LosslessValue var isAvailable: Bool
}
let json = #"{ "sku": 12345, "isAvailable": "true" }"#.data(using: .utf8)!
let result = try JSONDecoder().decode(Response.self, from: json)
print(result) // Response(sku: "12355", isAvailable: true)
日期包装器
在使用 Codable
时,解码具有混合日期格式的实体时经常遇到困扰。内置的 JSONDecoder
提供了一个便利的 dateDecodingStrategy
属性,但该属性为所有要解码的日期使用相同的日期格式。而且通常,JSONDecoder
与实体分离,如果选择使用其日期解码策略,则会与实体强制耦合。
属性包装器是解决上述问题的良好方案。它允许直接将日期格式化策略与实体的属性紧密绑定,并允许 JSONDecoder
保持与所解码实体的分离。该 @DateValue
包装器通用于自定义的 DateValueCodableStrategy
。这使得任何人都可以实现自己的日期解码策略,并获得免费的应用属性包装器行为。以下是一些常见的日期策略,但它们也成为了实现自定义属性包装器的模板,以满足特定的日期格式需求。
以下属性包装器深受 Ian Keen 的启发。
ISO8601Strategy
ISO8601Strategy
依赖于 ISO8601DateFormatter
将字符串值解码为日期。日期编码会将值编码回原始字符串值。
使用
struct Response: Codable {
@DateValue<ISO8601Strategy> var date: Date
}
let json = #"{ "date": "1996-12-19T16:39:57-08:00" }"#.data(using: .utf8)!
let result = try JSONDecoder().decode(Response.self, from: json)
// This produces a valid `Date` representing 39 minutes and 57 seconds after the 16th hour of December 19th, 1996 with an offset of -08:00 from UTC (Pacific Standard Time).
RFC3339Strategy
RFC3339Strategy
将 RFC 3339 日期字符串解码为日期。日期编码会将值编码回原始字符串值。
使用
struct Response: Codable {
@DateValue<RFC3339Strategy> var date: Date
}
let json = #"{ "date": "1996-12-19T16:39:57-08:00" }"#.data(using: .utf8)!
let result = try JSONDecoder().decode(Response.self, from: json)
// This produces a valid `Date` representing 39 minutes and 57 seconds after the 16th hour of December 19th, 1996 with an offset of -08:00 from UTC (Pacific Standard Time).
时间戳策略
《时间戳策略》将 Unix 纪元时间戳的 Double
值解码为 日期
。编码日期时,将值编码回原始的 时间段
值。
使用
struct Response: Codable {
@DateValue<TimestampStrategy> var date: Date
}
let json = #"{ "date": 978307200.0 }"#.data(using: .utf8)!
let result = try JSONDecoder().decode(Response.self, from: json)
// This produces a valid `Date` representing January 1st, 2001.
年月日策略
@DateValue<YearMonthDayStrategy>
使用日期格式 y-MM-dd
将字符串值解码为 日期
。编码日期时,将值编码回原始字符串格式。
使用
struct Response: Codable {
@DateValue<YearMonthDayStrategy> var date: Date
}
let json = #"{ "date": "2001-01-01" }"#.data(using: .utf8)!
let result = try JSONDecoder().decode(Response.self, from: json)
// This produces a valid `Date` representing January 1st, 2001.
最后,可以根据需要混合使用日期包装器以充分发挥其优势
struct Response: Codable {
@DateValue<ISO8601Strategy> var updatedAt: Date
@DateValue<YearMonthDayStrategy> var birthday: Date
}
let json = #"{ "updatedAt": "2019-10-19T16:14:32-05:00", "birthday": "1984-01-22" }"#.data(using: .utf8)!
let result = try JSONDecoder().decode(Response.self, from: json)
// This produces two valid `Date` values, `updatedAt` representing October 19, 2019 and `birthday` January 22nd, 1984.
安装
Swift 包管理器
归属
本项目采用 MIT 许可。如果您觉得这些工具很有用,请告诉您的老板您在哪里找到它们的。