通过属性包装器提升您的 Codable
结构体。 这些属性包装器的目标是避免实现自定义的 init(from decoder: Decoder) throws
并摆脱样板代码的困扰。
@LossyArray
解码数组,并在解码器无法解码值时过滤无效值。 当数组包含非可选类型,并且您的 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
解码字典,并在解码器无法解码值时过滤无效的键值对。 当字典旨在包含非可选值,并且您的 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
提供了一个通用的属性包装器,允许使用自定义的 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)
可选的布尔值很奇怪。 一种曾经表示 true 或 false 的类型,现在有三种可能的状态:.some(true)
、.some(false)
或 .none
。 如果做出 BadDecisions™,.none
条件可能表示真值。
如果解码器无法解码该值(无论是遇到 null 还是遇到某种意外类型),@DefaultFalse
通过将解码后的布尔值默认为 false 来缓解这种混乱。
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)
可选布尔值的怪异性也扩展到其他类型,例如数组。 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: [])
如前所述,可选字典是 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: [:])
所有功劳都归功于 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
依赖于 ISO8601DateFormatter
将 String
值解码为 Date
。 编码日期会将该值编码为原始字符串值。
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
将 RFC 3339 日期字符串解码为 Date
。 编码日期会将该值编码回原始字符串值。
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).
TimestampStrategy
将 unix epoch 的 Double
解码为 Date
。 编码日期会将该值编码为原始 TimeInterval
值。
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
将字符串值解码为 Date
。 编码日期会将该值编码回原始字符串格式。
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.
pod 'BetterCodable', '~> 0.1.0'
本项目在 MIT 许可下发布。 如果您觉得这些有用,请告诉您的老板您在哪里找到它们的。