Swift学习之Codable

作者: 冷武橘 | 来源:发表于2023-11-05 22:17 被阅读0次

    一、Codable

    typealias Codable = Decodable & Encodable
    

    Codable是一个同时符合 Decodable 和 Encodable 协议的类型,即可解码且可编码的类型。Codable 是Swift 4 引入的全新编码库,使用JSONDecoder可以实现字典转模型,使用JSONEncoder可以实现模型转字典。

    struct Good:Codable{
        var name:String
        var age:Int
    }
    
     func modelWithJson(){
            let dic = ["name": "张三","age":20] as [String : Any]
            let data = try! JSONSerialization.data(withJSONObject: dic)
            let jsonDecoder = JSONDecoder()
            let item = try! jsonDecoder.decode(Good.self, from: data)
        }
    
      func jsonWithModel(){
            let item = Good(name: "张三", age: 20)
            let jsonEnCoder = JSONEncoder()
            let data = try! jsonEnCoder.encode(item)
            if let dic = String.init(data: data, encoding: .utf8){
                print(dic)
            }
        }
    

    使用注意:属性age设置要成可选类型;如果json中没有包含age,非可选的话直接decoder模型会转换失败,而当age设置成可选类型时就可以成功成功转换

    struct Good:Codable{
        var name:String
        var age:Int?
    }
     let dic = ["name": "张三"] 
    

    1.2、CodingKeys

    在struct、class、enum中可以自定义一个CodingKeys( RawValue 为 String 类型,并符合 CodingKey 协议)枚举可以 用来

    • 1、当数据类型属性名和 JSON 中字段名不同时,做 key 的映射。
    • 2、通过在不添加某些字段的 case,可以忽略某个key不进行编码
    struct Good:Codable{
        var name:String
        var age:Int?
        enum CodingKeys:String,CodingKey{
            case  age
            case name = "nickName"
        }
    }
    

    1.3、嵌套对象

    只要这个嵌套对象也符合 Codable 协议,那整个对象就可以正常使用 JSONEncoder 和 JSONDecoder 编解码。

    struct Goods: Codable {
        var name: String
        var icon: String
    }
    
    struct Person: Codable {
        var good:Goods
        var age:Int
    }
    
      let dic = ["age":10,
                       "good":["name":"jack","icon":"hhh"]
                      ] as [String : Any]
      let data = try! JSONSerialization.data(withJSONObject: dic)
      let jsonDecoder = JSONDecoder()
      let item = try! jsonDecoder.decode(Person.self, from: data)
      print(item.good.name)
    

    二、自定义Codable

    class Student:Codable {
        var name:String
        var age:Int
        var selected:Bool
        enum CodingKeys:String,CodingKey{
            case name
            case age
            case selected
        }
        
        //自定义解码
        required init(from decoder: Decoder) throws {
            let container = try decoder.container(keyedBy: CodingKeys.self)
            self.name =  try container.decode(String.self, forKey: .name)
            self.age =  try container.decode(Int.self, forKey: .age)
            self.selected = try container.decode(Bool.self, forKey: .selected)
        }
        
        //自定义编码
        func encode(to encoder: Encoder) throws {
            var container =  encoder.container(keyedBy: CodingKeys.self)
            try! container.encode(self.name, forKey: CodingKeys.name)
            try! container.encode(self.age, forKey: CodingKeys.age)
            try! container.encode(self.selected, forKey: CodingKeys.selected)
            print(self.name)
            print(self.age)
            print(self.selected)
        }
    }
    

    以上就是自定义codeble,其中container是一个解析容器,通过container来调用decode、encoder实现编解码.
    Container在Decoder协议中知道,一共提供了三种:

    • KeyedDecodingContainer 代表容器中保存的数据是按照键值对的形式保存的
    • UnkeyedDecodingContainer 代表容器中保存的数据是没有键的,也就是说,保存的数据是一个数组
    • SingleValueDecodingContainer 代表容器中只保存了一个值。

    2.1、KeyedDecodingContainer

    一般JSON数据能被序列化成字典的,用的就是KeyedDecodingContainer来decode的。

      required init(from decoder: Decoder) throws { /// 通过从给定解码器解码来创建新实例。
            let container: KeyedDecodingContainer<Student.CodingKeys> = try decoder.container(keyedBy: CodingKeys.self)
            self.name =  try container.decode(String.self, forKey: .name)
            self.age =  try container.decode(Int.self, forKey: .age)
            self.selected =    self.selected = try container.decode(Bool.self, forKey: .selected)
            print(self.selected)
        }
    
      let json = """
                       {"name":"10","age":20,"selected":false}
                       """
            let data = json.data(using: .utf8)!
            
            let decode = JSONDecoder();
                
            let item = try! decode.decode(Student.self, from: data);
    
    

    2.2、UnkeyedDecodingContainer

    是没有键值的,保存的数据是一个数组。先举个例子说明,假如我们给我们的数据结构是这样的:["A",2,false],而我们想去分别对应a,b,c三个属性。我们就可以这样

    class Student:Codable {
        var a:String
        var b:Int
        var c:Bool
        //自定义解码
        required init(from decoder: Decoder) throws { /// 通过从给定解码器解码来创建新实例。
            var container = try decoder.unkeyedContainer()
            self.a =  try container.decode(String.self)
            self.b =  try container.decode(Int.self)
            self.c = try container.decode(Bool.self)
            print(self.a,self.b,self.c)
        }
    }
          let json = """
                        ["A",2,false]
                       """
            let data = json.data(using: .utf8)!
            let decode = JSONDecoder();
            let item = try! decode.decode(Student.self, from: data);
    
    

    UnkeyedDecodingContainer每次decode,下标会加1,所以会把数组里的值依次取出来赋值,所以多次decode,就可以给a,b,c依次赋值。

    2.3、SingleValueDecodingContainer

    SingleValueDecodingContainer中,container一般放的是String或者Int之类的,当然,也可以放数组字典,但是不在取里面的元素了,而是作为一个整体被解析。

    class Student:Codable {
        var name:String
        //自定义解码
        required init(from decoder: Decoder) throws { /// 通过从给定解码器解码来创建新实例。
            let container = try decoder.singleValueContainer()
            self.name = try container.decode(String.self)
            print(self.name)
         
        }
    }
    
      let json = """
                        "zhangsan"
                       """
            let data = json.data(using: .utf8)!
            
            let decode = JSONDecoder();
                
            let item = try! decode.decode(Student.self, from: data);
    
    

    以上就是将一个String存储到singleValueContainer进行解析

    //存放一个Int
    struct TestInt:Codable{
        var int:Int
        init(from decoder: Decoder) throws {
            
            self.int = try decoder.singleValueContainer().decode(Int.self)
            print(self.int)
        }
    }
    
    //存放一个Bool
    struct TestBool:Codable{
        var bool:Bool
        init(from decoder: Decoder) throws {
            self.bool = try decoder.singleValueContainer().decode(Bool.self)
            print(self.bool)
        }
    }
    
    //存放一个String
    struct TestString:Codable{
        var string:String
        init(from decoder: Decoder) throws {
            self.string = try decoder.singleValueContainer().decode(String.self)
            print(self.string)
        }
    }
    
    //存放一个字典
    struct TestDictionary:Codable{
        var dic:[String:String]
        init(from decoder: Decoder) throws {
            self.dic = try decoder.singleValueContainer().decode([String:String].self)
            print(self.dic)
        }
    }
    //存放一个数组
    struct TestArray:Codable{
        var array:[String]
        init(from decoder: Decoder) throws {
            self.array = try decoder.singleValueContainer().decode([String].self)
            print(self.array)
        }
    }
    func decoderTestInt(){
        let data = """
                   1
                   """
         .data(using: .utf8)!
        let decode = JSONDecoder();
        let p  = try! decode.decode(TestInt.self, from: data);
        
    }
    
    func decoderTestBool(){
        let data = """
                   false
                   """
         .data(using: .utf8)!
        let decode = JSONDecoder();
        let p  = try! decode.decode(TestBool.self, from: data);
        
    }
    
    
    func decoderTestString(){
        let data = """
                   "1"
                   """
         .data(using: .utf8)!
        let decode = JSONDecoder();
        let p  = try! decode.decode(TestString.self, from: data);
        
    }
    
    func decoderTestDic(){
        let data = """
                   {
                     "id":"222"
                   }
                   """
         .data(using: .utf8)!
        let decode = JSONDecoder();
        let p  = try! decode.decode(TestDictionary.self, from: data);
        
    }
    
    func decoderTestArray(){
        let data = """
                   ["aaa","bbb"]
                   """
         .data(using: .utf8)!
        let decode = JSONDecoder();
        let p  = try! decode.decode(TestArray.self, from: data);
        
    }
    

    以上三者的区别总的说区别如下:

    • KeyedDecodingContainer,通过字典key来取值
      let value = container[key]

    • UnkeyedDecodingContainer,通过数组下标来取值
      let value = container[index]

    • SingleValueDecodingContainer,value就是container本身
      let value = container

    三、自定义Codable的使用。

    尽管系统已帮我们默认实现了自定义编解码,但凡一个值decode或encoder失败就整个解析失败了。本地的数据我们严格编码自然是不会失败的,但是我们的数据一般是来自服务端的,服务端的数据类型和codable模型属性类型一个不匹配(例如 APP 端是 Int 类型,服务器下发的是 String 类型),或者是服务器下发的数据缺乏了某个字段,都会导致解析失败。这样事情我们是不希望发生的,所以就可以通过自定义Codable,在过程中进行容错处理,所以我觉得自定义Codable目前来说是十分必要的。

    3.1、提供默认值

    编译器自动生成的编解码实现有个问题就是不支持默认值。如果需要支持默认值就需要自己来用 decodeIfPresent 来实现:

    class Student:Codable{
        var name:String
        var age:Int
        var height:CGFloat
        required init(from decoder: Decoder) throws {
            let container = try decoder.container(keyedBy: CodingKeys.self)
            self.name = try container.decodeIfPresent(String.self, forKey: .name) ?? ""
            self.age = try container.decodeIfPresent(Int.self, forKey: .age) ?? 0
            self.height = try container.decodeIfPresent(CGFloat.self, forKey: .height) ?? 0.0
        }
    }
    

    使用decodeIfPresent 提供一个默认值,这样即使服务端的数据缺乏了某个字段或者某个数据为 null时,都不会影响正常解码,极大减少了jsonEncoder的错误率

    3.2、类型不一致强制处理

    @propertyWrapper
    struct DefaultSting:Codable{
        var wrappedValue: String
        init(from decoder: Decoder) throws {
            let container = try decoder.singleValueContainer()
            if let str = try? container.decode(Int.self){
                self.wrappedValue = String(str)
            }else if  let str = try? container.decode(String.self){
                self.wrappedValue = str
            } else{
                self.wrappedValue = ""
            }
        }
        init(){
            self.wrappedValue = "aaa"
        }
    }
    
    struct Student:Codable{
        @DefaultSting  var name:String
        var age:String
        init(from decoder: Decoder) throws {
            let container = try decoder.container(keyedBy: CodingKeys.self)
            self._name = try container.decodeIfPresent(DefaultSting.self, forKey: .name) ?? DefaultSting()
            self.age = try container.decodeIfPresent(String.self, forKey: .age) ?? ""
        }
    }
    

    改进版:

    import UIKit
    protocol DefaultValue {
        static var defaultValue: Self { get set}
    }
    
    extension String: DefaultValue {
       static var  defaultValue = ""
    }
    
    extension Int: DefaultValue {
        static var defaultValue = 0
    }
    extension Float: DefaultValue {
        static var defaultValue:Float = 0.0
    }
    extension Bool: DefaultValue {
        static var defaultValue:Bool = false
    }
    extension Double: DefaultValue {
        static var defaultValue:Double = 0.0
    }
    extension CGFloat: DefaultValue {
        static var defaultValue:CGFloat = 0.0
    }
    typealias DefaultCodable = DefaultValue & Codable
    
    @propertyWrapper
    struct Default<T: DefaultCodable> {
        var wrappedValue: T
    }
    
    extension Default: Codable {
        init(from decoder: Decoder) throws {
            let container = try decoder.singleValueContainer()
            if T.self == String.self{
                if let num  = try? container.decode(Int.self){
                    wrappedValue = "\(num)" as! T
                }else if let num = try? container.decode(Bool.self){
                    wrappedValue = (num == true ? "0" : "1") as! T
                }else if let num = try? container.decode(Double.self){
                    wrappedValue  = "\(num)" as! T
                }else if let num = try? container.decode(Float.self){
                    wrappedValue  = "\(num)" as! T
                }else{
                    wrappedValue = (try? container.decode(T.self)) ?? T.defaultValue
                }
            }else if T.self == Int.self{
                if let value  = try? container.decode(Int.self){
                    wrappedValue = Int(value) as! T
                }else
                if let value = try? container.decode(Bool.self){
                    wrappedValue = (value == true ? 0 : 1) as! T
                }else if let value = try? container.decode(Double.self){
                    wrappedValue = Int(value)  as! T
                }else if let value = try? container.decode(Float.self){
                    wrappedValue = Int(value)  as! T
                }else if let value = try? container.decode(String.self){
                    wrappedValue = (Int(value) ?? 0)  as! T
                }else{
                    wrappedValue = (try? container.decode(T.self)) ?? T.defaultValue
                }
            }else if T.self == Float.self{
                if let value  = try? container.decode(Int.self){
                    wrappedValue = Float(value) as! T
                }else if let value = try? container.decode(Double.self){
                    wrappedValue = Float(value) as! T
                }else if let value = try? container.decode(Float.self){
                    wrappedValue = Float(value) as! T
                }
                else if let value = try? container.decode(String.self){
                    wrappedValue = String(value) as! T
                }else{
                    wrappedValue = (try? container.decode(T.self)) ?? T.defaultValue
                }
            }else if T.self == CGFloat.self{
                if let value  = try? container.decode(Int.self){
                    wrappedValue = CGFloat(value) as! T
                }else if let value = try? container.decode(Double.self){
                    wrappedValue = CGFloat(value) as! T
                }else if let value = try? container.decode(Float.self){
                    wrappedValue = CGFloat(value) as! T
                }
                else if let value = try? container.decode(String.self){
                    let double = Double(value)
                    wrappedValue = CGFloat(double ?? 0.00) as! T
                }else{
                    wrappedValue = (try? container.decode(T.self)) ?? T.defaultValue
                }
            }
            else if T.self == Bool.self {
                if let value  = try? container.decode(Int.self){
                    wrappedValue = (value == 0 ? false : true) as! T
                }
                else if let value = try? container.decode(String.self){
                    wrappedValue = (value == "0" ? false : true) as! T
                }else{
                    wrappedValue = (try? container.decode(T.self)) ?? T.defaultValue
                }
            }else if T.self == [Any].self{
                wrappedValue = try! container.decode(T.self) as! T
            }
            
            else{
             
                
                wrappedValue = try! container.decode(T.self) as! T
            }
            
            
        }
    
        func encode(to encoder: Encoder) throws {
            var container = encoder.singleValueContainer()
            try container.encode(wrappedValue)
        }
    }
    
    extension KeyedDecodingContainer {
        func decode<T>(_ type: Default<T>.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> Default<T> where T : DefaultCodable {
            try decodeIfPresent(type, forKey: key) ?? Default(wrappedValue: T.defaultValue)
        }
    }
    
    
    extension UnkeyedDecodingContainer {
        mutating func decode<T>(_ type: Default<T>.Type) throws -> Default<T> where T : DefaultCodable {
            try decodeIfPresent(type) ?? Default(wrappedValue: T.defaultValue)
        }
        
    }
    
    
    struct DefaultArray<T:Codable>:Codable{
        private(set) var contens:[T] = [T]()
        var data:String?
        init(from decoder: Decoder) throws {
            var unkeyedContaines:UnkeyedDecodingContainer! = nil
            if  let  container = try? decoder.container(keyedBy:CodingKeys.self) {
                unkeyedContaines = try?
                    container.nestedUnkeyedContainer(forKey:CodingKeys.data)
                print("")
            }else{
                unkeyedContaines = try decoder.unkeyedContainer()
            }
            guard unkeyedContaines != nil else{
                print("解析失败")
                return
            }
            print("解析成功")
            while !unkeyedContaines.isAtEnd {
                let a = try unkeyedContaines.decode(T.self)
                self.contens.append(a)
            }
        }
    }
    
    
    struct JSONConver<T:Codable>{
        //字典转模型
        static func model(_ dic:[String:Any]) -> T{
            let data = try! JSONSerialization.data(withJSONObject: dic)
            let jsondecoder = JSONDecoder()
            //jsondecoder.dataDecodingStrategy = JSONDecoder.secondsSince1970
            let item = try! jsondecoder.decode(T.self, from: data)
            return item
        }
        //key:data ,value任意一数组,如果你想更改key为别的字段,需要把以上DefaultArray的var data:String?更改自己需要的
        static func modelArray(_ array:[Any]) -> [T]{
           let data = try! JSONSerialization.data(withJSONObject: array)
           let jsondecoder = JSONDecoder()
            let arr = try! jsondecoder.decode(DefaultArray<T>.self, from: data).contens
            return arr
        }
        //字典数组转模型数组
        static func modelArray(_ dic:[String:Any]) -> [T]{
           let data = try! JSONSerialization.data(withJSONObject: dic)
           let jsondecoder = JSONDecoder()
            let arr = try! jsondecoder.decode(DefaultArray<T>.self, from: data).contens
            return arr
        }
        
    }
    //模型嵌套直接
    struct Person:Codable{
        @Default  var p:String
    }
    
    
    struct Student:Codable{
        @Default  var name:String
        var p:[Person]
        
    }
    

    参考学习:https://juejin.cn/post/6938388060367224869

    相关文章

      网友评论

        本文标题:Swift学习之Codable

        本文链接:https://www.haomeiwen.com/subject/kshtwdtx.html