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

1,453 阅读2分钟

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 & Codable



public struct LosslessDefaultStrategy<Value: LosslessStringCodable>{
    public static var losslessDecodableTypes: [(Decoder) -> LosslessStringCodable?] {
        @inline(__always)
        func decode<T: LosslessStringCodable>(_: 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<Strategy: LosslessDecodingStrategy>: Codable {
    private let type: LosslessStringCodable.Type

    public var wrappedValue: Strategy.Value

    public init(wrappedValue: Strategy.Value) {
        self.wrappedValue = 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 = value
                    self.type = Swift.type(of: rawVal)
                    return
                }
            }
            throw error
        }
    }
}

因为上文大量使用范型,

定义,使用默认解码方式 LosslessDefaultStrategy , 的属性装饰器

public typealias LosslessValue<T> = LosslessValueCodable<LosslessDefaultStrategy<T>> 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 = 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 = 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 = try? JSONSerialization.data(withJSONObject: dict, options: []), let model = try? JSONDecoder().decode(Person.self, from: jsonData){
            print(model.name.strVal, " - ", model.age.intVal)
        }

github repo