Resilient Decoding
简介
本包定义了在解码 Decodable
类型时从错误中部分恢复的机制。它还旨在提供可 ergonomics 的 API,用于在开发过程中检查解码错误并在生产中报告它们。
更多详情见下,但以下是本包能实现的功能一瞥:
struct Foo: Decodable {
@Resilient var array: [Int]
@Resilient var value: Int?
}
let foo = try JSONDecoder().decode(Foo.self, from: """
{
"array": [1, "2", 3],
"value": "invalid",
}
""".data(using: .utf8)!)
运行此代码后,foo
将成为 Foo
,其中 foo.array == [1, 3]
和 foo.value == nil
。在 DEBUG 中,foo.$array.results
将是 [.success(1), .failure(DecodingError.dataCorrupted(…), .success(3)]
,而 foo.$value.error
将是 DecodingError.dataCorrupted(…)
。该功能仅为 DEBUG
所用,这样我们就可以在发布构建中保持 无开销。
设置
Swift 包管理器
在您的Package.swift
文件中
dependencies: [
.package(name: "ResilientDecoding", url: "https://github.com/airbnb/ResilientDecoding.git", from: "1.0.0"),
]
CocoaPods
在您的Podfile
文件中
platform :ios, '12.0'
pod 'ResilientDecoding', '~> 1.0'
Decoding
此包的主要接口是@Resilient
属性包装器。它可以应用于四种类型的属性:Optional
、Array
、Dictionary
以及遵循本包提供的ResilientRawRepresentable
协议的自定义类型。
Optional
Optional
是最简单的属性类型之一,可以被转换为Resilient
。写出@Resilient var foo: Int?
形式的属性将被初始化为nil
,如果在解码过程中遇到错误(例如,如果foo
键的值是
Array
Resilient
也可以应用于数组或可选数组([T]?
)。如果属性以@Resilient var foo: [Int]
的形式编写,当foo
键不存在或者值不是预期的(例如,String)时,会初始化一个空数组。同样,如果此数组的任何元素在解码过程中失败,将省略该元素。这个可选数组变体会将值设置为nil
(如果键不存在或具有null值),否则为空数组。
Dictionary
“Resilient” 也可以应用于(字符串键)字典或可选字典([String: T]?
)。以 @Resilient var foo: [String: Int]
形式编写的属性,如果 foo
键不存在或值为意外值(如 String
),则初始化为空字典。同样,如果字典中任何 值 解码失败,该值将被省略。此可选字典的变体将在键不存在或具有 null 值时将值设置为 nil
,否则为空数组。
ResilientRawRepresentable
自定义类型可以实现 ResilientRawRepresentable
协议,允许它们自定义 在被解码为 Resilient
属性时的行为(否则没有影响)。ResilientRawRepresentable
继承自 RawRepresentable
,主要用于具有原始值的 enum
的适配。它有两个静态属性:decodingFallback
和 isFrozen
。
decodingFallback
ResilientRawRepresentable
类型可以定义可选的 decodingFallback
,允许其在无需包裹在可选值的情况下进行弹性解码。例如,以下枚举可用于属性编写 @Resilient var myEnum: MyEnum
enum MyEnum: String, ResilientRawRepresentable {
case existing
case unknown
static var decodingFallback: Self { .unknown }
}
注意: Array
和 Dictionary
的 ResilientRawRepresentable
总是省略元素,而不是使用 decodingFallback
。
isFrozen
isFrozen
控制是否将新 RawValues
报告错误到 ResilientDecodingErrorReporter
。默认情况下,isFrozen
设置为 false
,这意味着返回 nil
的 init(rawValue:)
的 RawValue
将 不会 报告错误。当你希望旧版本的代码在不报告错误的情况下支持新的代码时,这很有用,例如,当演变支持 iOS 应用程序的后端 API。以这种方式,该属性类似于 Swift 的 @frozen
属性,尽管它们实现不同的目标。isFrozen
对属性级错误没有影响。
检查错误
Resilient
提供了两种检查错误的机制,一种用于开发期间,另一种用于在生产中报告意外错误。
属性级别错误
在 DEBUG
构建中,Resilient
属性提供了一个包含在解码过程中遇到错误信息的 projectedValue
。可以使用 $property.outcome
属性检查此信息,该属性是一个包含 keyNotFound
和 valueWasNil
等情况的枚举。这与错误不同,因为当属性值为 Optional
时,上述两种情况实际上是错误的,例如。标量类型,如 Optional
和 ResilientRawRepresentable
,还提供了一个 error
属性。开发人员可以通过访问 $foo.error
来确定解码属性时是否发生了错误,其中属性写入 @Resilient var foo: Int?
。 @Resilient
数组属性提供两个额外的字段: errors
和 results
。 errors
是解码数组时从错误中恢复的所有错误列表。results
将这些错误与成功解码的数组元素交织在一起。例如,当解码具有 @Resilient var baz: [Int]
的属性时,JSON片段 [1, 2, "3"]
的 results
将是两个 .success
值,后面跟着一个 .failure
。
ResilientDecodingErrorReporter
在生产中,可以使用 ResilientDecodingErrorReporter
来汇总在解码具有 Resilient
属性的类型时遇到的全部错误。JSONDecoder
提供了一个方便的 decode(_:from:reportResilientDecodingErrors:)
API,如果遇到错误,它将返回解码值和错误摘要。更复杂的用例需要将 ResilientDecodingErrorReporter
添加到 Decoder
的 userInfo
中,作为 .resilientDecodingErrorReporter
用户信息键的值。解码类型后,您可以调用 flushReportedErrors
,如果遇到错误,它将返回一个 ErrorDigest
。摘要可以用来访问底层错误(errorDigest.errors
)或在 DEBUG
中格式化打印(debugPrint(errorDigest)
)。
格式化打印的摘要看起来是这样的:
resilientArrayProperty
Index 1
- Could not decode as `Int`
Index 3
- Could not decode as `Int`
resilientRawRepresentableProperty
- Unknown novel value "novel" (this error is not reported by default)
注意:属性包装器上可用的错误与报告给 ResilientDecodingErrorReporter
的错误有一个区别:后者默认 不 报告 UnknownNovelValueError
(当非冻结的 ResilientRawRepresentable
的 init(rawValue:)
返回 nil
时抛出 UnknownNovelValueError
)。您可以通过在错误摘要上调用 errors(includeUnknownNovelValueErrors: true)
来改变这种行为。
常见问题
Resilient
能否按预期工作?
当封装类型为泛型参数时,不。如果你有一个泛型类型,其泛型参数为<T>
,并指定@Resilient var someResilient: T
,无论T
是一个数组还是字典,它都会被视为单个值。
更多信息
有关特定Resilient
字段遇到特定错误时如何表现的更多信息,建议查阅单元测试。