上回提到 Enum with RawValue 的 Decodable 处理,最近发现了更方便的处理方式,这里总结一下。
第一版
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| public enum Category1: String, Decodable {
case left
case right }
public struct Test1: Decodable {
public let category1: Category1
public let int: Int }
let json11 = """ { "category1": "left", "int": 1 } """.data(using: .utf8)!
let json12 = """ { "category1": "east", "int": 1 } """.data(using: .utf8)!
do { let decoder = JSONDecoder() let model11 = try decoder.decode(Test1.self, from: json11) print(model11) } catch { print(error) }
do { let decoder = JSONDecoder() let model12 = try decoder.decode(Test1.self, from: json12) print(model12) } catch { print(error) }
|
当 category1
返回一个 Category1
未定义值的时候,解析出错。
第二版
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public struct Test2: Decodable {
public let category1: Category1?
public let int: Int }
do { let decoder = JSONDecoder() let model22 = try decoder.decode(Test2.self, from: json12) print(model22) } catch { print(error) }
|
即使 category1
声明为 Category1?
,返回未定义值的时候,仍然解析出错。
第三版
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| public enum Category3: String, Decodable {
case left
case right
case unknown
public init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() let rawValue = try container.decode(String.self) self = Category3(rawValue: rawValue) ?? .unknown } }
public struct Test3: Decodable {
public let category1: Category3
public let int: Int }
do { let decoder = JSONDecoder() let model32 = try decoder.decode(Test3.self, from: json12) print(model32) } catch { print(error) }
|
给 Category3
增加 unknown
成员并重写 init(from:)
方法,可以在返回未定义值的时候回退到 unknown
,防止解析出错。
如果每一个 Enum with RawValue 都需要单独增加 unknown
成员和重写 init(from:)
方法,无疑是低效的。
我们都知道,Enum with RawValue 默认实现了 RawRepresentable
协议。我们可以从这里入手,通过强大的 extension
来避免模板代码。
第四版
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| public protocol UnknownCompatible: RawRepresentable {
static var unknown: Self { get } }
extension UnknownCompatible where Self: Decodable, RawValue: Decodable {
public init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() let rawValue = try container.decode(RawValue.self) self = Self(rawValue: rawValue) ?? .unknown } }
public enum Category4: String, UnknownCompatible, Decodable {
case left
case right
case unknown }
public struct Test4: Decodable {
public let category1: Category4
public let int: Int }
do { let decoder = JSONDecoder() let model42 = try decoder.decode(Test4.self, from: json12) print(model42) } catch { print(error) }
|
声明一个继承 RawRepresentable
协议的 UnknownCompatible
协议,协议只约束了一个 unknown
静态计算属性。给 UnknownCompatible
增加 init(from")
的默认实现,要求是 Self
和 RawValue
实现 Decodable
协议。
在 Category4
中,只需要声明实现 UnknownCompatible
协议,即可免费获得上面的初始化方法。这里用到了 Swift 5.3 的 SE-0230 Enum cases as protocol witnesses 提案。
自定义
上面的问题,对应的是 DecodingError.dataCorrupted
错误。除此之外,还有 keyNotFound
/ typeMismatch
/ valueNotFound
错误。我们可以通过自定义来处理部分错误,并且回退到 unknown
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
| public protocol UnknownCompatible2: RawRepresentable {
static var unknown: Self { get }
static var allowDataCorrupted: Bool { get }
static var allowTypeMismatch: Bool { get }
static var allowValueNotFound: Bool { get } }
extension UnknownCompatible2 {
public static var allowDataCorrupted: Bool { true }
public static var allowTypeMismatch: Bool { false }
public static var allowValueNotFound: Bool { true } }
extension UnknownCompatible2 where Self: Decodable, RawValue: Decodable {
public init(from decoder: Decoder) throws { do { let container = try decoder.singleValueContainer() let rawValue = try container.decode(RawValue.self)
if let value = Self(rawValue: rawValue) { self = value } else if Self.allowDataCorrupted { self = .unknown } else { throw DecodingError.dataCorruptedError( in: container, debugDescription: "Cannot initialize \(Self.self) from invalid \(RawValue.self) value \(rawValue)" ) } } catch DecodingError.typeMismatch where Self.allowTypeMismatch { self = .unknown } catch DecodingError.valueNotFound where Self.allowValueNotFound { self = .unknown } catch { throw error } } }
public enum Category5: String, UnknownCompatible2, Decodable {
case left
case right
case unknown
public static var allowTypeMismatch: Bool = true }
public struct Test5: Decodable {
public let category1: Category5
public let category2: Category5
public let category3: Category5 }
let json2 = """ { "category1": "east", "category2": true, "category3": null } """.data(using: .utf8)!
do { let decoder = JSONDecoder() let model5 = try decoder.decode(Test5.self, from: json2) print(model5) } catch { print(error) }
|
除了 keyNotFound
错误,其余三个错误都可以在 UnknownCompatible2
协议的 init(from:)
默认实现中得到处理。这里用到了 Swift 5.3 的 SE-0276 Multi-Pattern Catch Clauses 提案。
总结
通过 Swift 强大的协议和扩展系统,我们可以避免书写重复的模板代码。但应该注意,统一实现需要处理好边界条件,不然一步错,步步错。
小尾巴
decode
参数直接放在枚举类型下面,点语法访问时有点别扭,可以考虑放在 DecodingOption
这样的结构体里,或者使用 Property Wrapper 来自定义 decode
行为。或许下一次可以写一下(先咕咕咕再说)。
参考