解码多种可能类型的两种技巧(属性包装器)

Swift 的属性包装器是语法糖,
他可以让代码更加的声明式
定义属性的时候,把规则写进去

属性包装器, 主要使用 wrappedValue, 有时候用到 projectedValue
问题:
存在这样的一个结构体:
struct Figure: Decodable { var name: String var age: Int }

后端传过来,一个 json,
要求 json 对应的内容,长这样,
能够解析出来
var dict: [String: Any] = ["name": "666", "age": 666]

json 对应的内容,长这样,
也可解析出来
dict = ["name": 666, "age": "666"]

问题,解码一个属性,他的数据可能有多种类型 技巧一,使用属性包装器
提供解码方法
static var losslessDecodableTypes:, 是一个解码方法的集合
系统提供的 LosslessStringConvertible 协议,可以让字符串,转值
public typealias LosslessStringCodable = LosslessStringConvertible & Codablepublic struct LosslessDefaultStrategy{ public static var losslessDecodableTypes: [(Decoder) -> LosslessStringCodable?] { @inline(__always) func decode(_: T.Type) -> (Decoder) -> LosslessStringCodable? { return { try? T.init(from: $0) } }return [ decode(String.self), decode(Int.self), decode(Double.self), decode(Float.self) ] } }

通过属性包装器,
  • 解码的时候,先使用属性对应类型,进行解码
  • 出错了,再使用上面指定的各种类型的解码器,都解码一遍
上面的 decode(Float.self), 可以删
因为 Double 的精度更好,走不到使用 Float 解码
public protocol LosslessDecodingStrategy { associatedtype Value: LosslessStringCodable/// An ordered list of decodable scenarios used to infer the encoded type static var losslessDecodableTypes: [(Decoder) -> LosslessStringCodable?] { get } }@propertyWrapper public struct LosslessValueCodable: Codable { private let type: LosslessStringCodable.Typepublic var wrappedValue: Strategy.Valuepublic init(wrappedValue: Strategy.Value) { self.wrappedValue = https://www.it610.com/article/wrappedValue self.type = Strategy.Value.self }public init(from decoder: Decoder) throws { do { self.wrappedValue = try Strategy.Value.init(from: decoder) self.type = Strategy.Value.self } catch let error { for block in Strategy.losslessDecodableTypes{ if let rawVal = block(decoder), let value = Strategy.Value.init("\(rawVal)"){ self.wrappedValue = https://www.it610.com/article/value self.type = Swift.type(of: rawVal) return } } throw error } } }

因为上文大量使用范型,
定义,使用默认解码方式 LosslessDefaultStrategy , 的属性装饰器
public typealias LosslessValue = LosslessValueCodable> where T: LosslessStringCodable

调用部分 属性定义
struct Figure: Decodable { @LosslessValue var name: String @LosslessValue var age: Int }

数据解码
var dict: [String: Any] = ["name": "666", "age": 666] dict = ["name": 666, "age": "666"] if let jsonData = https://www.it610.com/article/try? JSONSerialization.data(withJSONObject: dict, options: []), let model = try? JSONDecoder().decode(Figure.self, from: jsonData){ print(model.name," - ", model.age) }

技巧 2 ,糙一些,制定自定义解码
代码: 代码很幼稚,
使用各种类型,去解码,
没有使用范型,使用枚举的多种类型,去获取值
使用值的时候,就将 self 拆包,可能会强转
不声明式, 看代码,不知道属性的具体类型
调用的时候,根据业务,去使用对应类型的值
【解码多种可能类型的两种技巧(属性包装器)】属性包装器更加优雅,直接使用,隐藏了 wrappedValue
enum VariousTypeErr: Error { case miss }enum VariousType: Decodable{case aDouble(Double), aFloat(Float), aInt(Int), aString(String)init(from decoder: Decoder) throws {if let int = try? decoder.singleValueContainer().decode(Int.self) { self = .aInt(int) return }if let value = https://www.it610.com/article/try? decoder.singleValueContainer().decode(Double.self) { self = .aDouble(value) return }if let gotIt = try? decoder.singleValueContainer().decode(Float.self) { self = .aFloat(gotIt) return }if let text = try? decoder.singleValueContainer().decode(String.self) { self = .aString(text) return }throw VariousTypeErr.miss }var strVal: String{ switch self { case .aDouble(let val): return String(val) case .aFloat(let val): return String(val) case .aInt(let val): return String(val) case .aString(let val): return val } }var intVal: Int{ switch self { case .aDouble(let val): return Int(val) case .aFloat(let val): return Int(val) case .aInt(let val): return val case .aString(let val): return Int(val) ?? 0 } } }

调用:
struct Person: Decodable { var name: VariousType var age: VariousType }var dict: [String: Any] = ["name": "666", "age": 666] dict = ["name": 666, "age": "666"]if let jsonData = https://www.it610.com/article/try? JSONSerialization.data(withJSONObject: dict, options: []), let model = try? JSONDecoder().decode(Person.self, from: jsonData){ print(model.name.strVal," - ", model.age.intVal) }

github repo

    推荐阅读