一步步学习Swift

Swift学习之Codable

2023-11-05  本文已影响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 协议)枚举可以 用来

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协议中知道,一共提供了三种:

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);
    
}

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

三、自定义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

上一篇下一篇

猜你喜欢

热点阅读