关于CodableWrapper
- Codable很好,但是有一些缺陷:比如严格要求数据源,定义为String给了Int就抛异常、支持自定义CodingKey但是写法十分麻烦、缺字段的情况下不使用Optional会抛异常而不是使用缺省值等等。
- 之前发布过PropertyWrapper版,主要使用PropertyWrapper标记属性来提高了Codable的使用体验,使用了几个比较tricky的黑科技,所以API也比市面上的同类库要简单。
- 现在Swift5.9支持宏了,决定写一个宏版本的CodableWrapper。目前已开发完毕Github - CodableWrapper/swift5.9-macro
首先确定目标
- 支持缺省值,JSON缺少字段容错
- 支持
StringBoolNumber等基本类型互转 - 驼峰大小写自动互转
- 自定义解析key
- 自定义解析规则 (Transformer)
- 方便的
Codable Class子类
设计API
-
定义宏:
@Codable@CodableSubclass@CodableKey(..)@CodableNestedKey(..)@CodableTransformer(..)
-
例子:
@Codable struct BasicModel { var defaultVal: String = "hello world" var defaultVal2: String = Bool.random() ? "hello world" : "" let strict: String let noStrict: String? let autoConvert: Int? @CodableKey("hello") var hi: String = "there" @CodableNestedKey("nested.hi") @CodableTransformer(StringPrefixTransform("HELLO -> ")) var codingKeySupport: String @CodableNestedKey("nested.b") var nestedB: String var testGetter: String { nestedB } }
手动实现这些目标特性
想要宏自动生成代码,需要先弄清楚怎么手动实现这些目标特性。先想象一下编译器怎么生成Codable的实现的:
struct BasicModel: Codable {
var defaultVal: String = "hello world"
}
// 编译器生成的
struct BasicModel: Codable {
var defaultVal: String = "hello world"
enum CodingKeys: String, CodingKey {
case defaultVal
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.defaultVal = try container.decode(String.self, forKey: .defaultVal)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.defaultVal, forKey: .defaultVal)
}
}
手动实现目标1:支持缺省值
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.defaultVal = (try? container.decode(String.self, forKey: .defaultVal)) ?? "hello world"
}
手动实现目标2:支持 String Bool Number 等基本类型互转
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if let value = try? container.decode(Int.self, forKey: .defaultVal) {
self.defaultVal = String(value)
} else if let value = try? container.decode(String.self, forKey: .defaultVal) {
self.defaultVal = String(value)
} else if let value = try? container.decode(Float.self, forKey: .defaultVal) {
self.defaultVal = String(value)
}
// 各种基本类型尝试转换 else if ...
else {
self.defaultVal = "hello world"
}
}
这么解析肯定不是个办法,这里结合了两个开源库的实现,第一步使用AnyDecodable解析出JSON内的数据,解析出来可能是任意基本类型。第二步使用BuiltInBridgeType和BuiltInBridgeType将解析出来的基本类型尝试转换成目标类型。完整实现如下:
extension Decodable {
static func decode<K>(from container: KeyedDecodingContainer<K>, forKey key: KeyedDecodingContainer<K>.Key) throws -> Self {
return try container.decode(Self.self, forKey: key)
}
}
struct BasicModel: Codable {
var defaultVal: String = "hello world"
enum CodingKeys: CodingKey {
case defaultVal
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
defaultVal = container.tryNormalKeyDecode(type: String.self, key: .defaultVal) ?? "hello world"
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(defaultVal, forKey: .defaultVal)
}
}
extension KeyedDecodingContainer {
func tryNormalKeyDecode<Value>(type _: Value.Type, key: K) -> Value? {
let value = try? decodeIfPresent(AnyDecodable.self, forKey: key)?.value
if let value = value {
if let converted = value as? Value {
return converted
}
if let _bridged = (Value.self as? _BuiltInBridgeType.Type)?._transform(from: value), let __bridged = _bridged as? Value {
return __bridged
}
// 如果是非基本类型,那继续尝试Decode
if let valueType = Value.self as? Decodable.Type {
if let value = try? valueType.decode(from: self, forKey: key) as? Value {
return value
}
}
}
return nil
}
}
手动实现目标3:支持驼峰大小写自动互转
// 为了简化CodingKey,使用AnyCodingKey实现CodingKey
public struct AnyCodingKey: CodingKey {
public var stringValue: String
public var intValue: Int?
public init?(stringValue: String) {
self.stringValue = stringValue
intValue = nil
}
public init?(intValue: Int) {
stringValue = "\(intValue)"
self.intValue = intValue
}
public init(index: Int) {
stringValue = "\(index)"
intValue = index
}
}
struct BasicModel: Codable {
var defaultVal: String = "hello world"
init(from decoder: Decoder) throws {
// CodingKeys 改为 AnyCodingKey
let container = try decoder.container(keyedBy: AnyCodingKey.self)
defaultVal = container.tryNormalKeyDecode(type: String.self, key: "defaultVal") ?? "hello world"
}
}
extension KeyedDecodingContainer where K == AnyCodingKey {
func tryNormalKeyDecode<Value>(type: Value.Type, key: String) -> Value? {
func _decode(key: String) -> Value? {
let value = try? decodeIfPresent(AnyDecodable.self, forKey: key)?.value
if let value = value {
if let converted = value as? Value {
return converted
}
if let _bridged = (Value.self as? _BuiltInBridgeType.Type)?._transform(from: value), let __bridged = _bridged as? Value {
return __bridged
}
if let valueType = Value.self as? Decodable.Type {
if let value = try? valueType.decode(from: self, forKey: key) as? Value {
return value
}
}
}
return nil
}
for newKey in [key, key.snakeCamelConvert()].compactMap({ $0 }) {
if let value = _decode(key: newKey) {
return value
}
}
return nil
}
}
extension String {
func snakeCamelConvert() -> String? {
// 驼峰大小写互转
...
}
}
手动实现目标4:自定义解析Key和NestedKey
container.decode封装 container.encode封装
struct TestModel: Codable {
// @CodableKey("u1", "u2", "u9")
let userName: String = ""
// @CodableNestedKey("data.u1", "data.u2", "data.u9")
let userName2: String = ""
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: AnyCodingKey.self)
userName = container.decode(type: String.self, keys: ["u1", "u2", "u9"], nestedKeys: []) ?? ""
userName2 = container.decode(type: String.self, keys: [], nestedKeys: ["data.u1", "data.u2", "data.u9"]) ?? ""
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(value: self.userName, keys: ["u1", "u2", "u9"], nestedKeys: [])
try container.encode(value: self.userName2, keys: [], nestedKeys: ["data.u1", "data.u2", "data.u9"])
}
}
手动实现目标5:自定义解析规则Transformer
// 定义TransformType协议
public protocol TransformType {
associatedtype Object
associatedtype JSON: Codable
func transformFromJSON(_ json: JSON?) -> Object
func transformToJSON(_ object: Object) -> JSON?
}
// 定义一个不遵循Codable的结构体
struct DateWrapper {
let timestamp: TimeInterval
var date: Date {
Date(timeIntervalSince1970: timestamp)
}
init(timestamp: TimeInterval) {
self.timestamp = timestamp
}
static var transformer = TransformOf<DateWrapper, TimeInterval>(fromJSON: { DateWrapper(timestamp: $0 ?? 0) }, toJSON: { $0.timestamp })
}
struct DateModel: Codable {
// @CodableTransformer(DateWrapper.transformer)
var time: DateWrapper? = DateWrapper(timestamp: 0)
// @CodableTransformer(DateWrapper.transformer)
var time1: DateWrapper = .init(timestamp: 0)
// @CodableTransformer(DateWrapper.transformer)
var time2: DateWrapper?
// @CodableTransformer(DateWrapper.transformer)
var time3: DateWrapper
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: AnyCodingKey.self)
let time = try? container.decode(type: TimeInterval.self, keys: ["time"], nestedKeys: [])
self.time = DateWrapper.transformer.transformFromJSON(time) ?? DateWrapper(timestamp: 0)
let time1 = try? container.decode(type: TimeInterval.self, keys: ["time1"], nestedKeys: [])
self.time1 = DateWrapper.transformer.transformFromJSON(time1) ?? .init(timestamp: 0)
let time2 = try? container.decode(type: TimeInterval.self, keys: ["time2"], nestedKeys: [])
self.time2 = DateWrapper.transformer.transformFromJSON(time2)
let time3 = try? container.decode(type: TimeInterval.self, keys: ["time3"], nestedKeys: [])
self.time3 = DateWrapper.transformer.transformFromJSON(time3)
}
func encode(to encoder: Encoder) throws {
let container = encoder.container(keyedBy: AnyCodingKey.self)
if let time = self.time, let value = DateWrapper.transformer.transformToJSON(self.time) {
try container.encode(value: value, keys: ["time"], nestedKeys: [])
}
if let value = DateWrapper.transformer.transformToJSON(self.time1) {
try container.encode(value: value, keys: ["time1"], nestedKeys: [])
}
if let time2 = self.time2, let value = DateWrapper.transformer.transformToJSON(time2) {
try container.encode(value: value, keys: ["time2"], nestedKeys: [])
}
if let value = DateWrapper.transformer.transformToJSON(time3) {
try container.encode(value: value, keys: ["time3"], nestedKeys: [])
}
}
}
手动实现目标6:“不方便”的 Codable Class 子类
class ClassModel1: Codable {
var val: String?
}
class ClassSubmodel1: ClassModel1 {
var subVal: String = "1_1"
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: AnyCodingKey.self)
self.subVal = (try? container.decode(type: type(of: self.subVal), keys: ["subVal"], nestedKeys: [])) ?? ("1_1")
try super.init(from: decoder)
}
override func encode(to encoder: Encoder) throws {
try super.encode(to: encoder)
let container = encoder.container(keyedBy: AnyCodingKey.self)
try container.encode(value: self.subVal, keys: ["subVal"], nestedKeys: [])
}
}
下一篇文章将介绍Codable宏的开发和实现。
文章目录