Swift学习之Codable

一、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 = 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 {
    var wrappedValue: T
}

extension Default: Codable {
    
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        if T.defaultValue is String{
            if let value  = try? container.decode(Int.self){
                T.defaultValue = String(value) as! T
            }else 
            if let value = try? container.decode(Bool.self){
                T.defaultValue = (value == true ? "0" : "1") as! T
            }else if let value = try? container.decode(Double.self){
                T.defaultValue = String(value) as! T
            }else if let value = try? container.decode(Float.self){
                T.defaultValue = String(value) as! T
            }
        }else if T.defaultValue is Int{
            if let value  = try? container.decode(Int.self){
                T.defaultValue = Int(value) as! T
            }else
            if let value = try? container.decode(Bool.self){
                T.defaultValue = (value == true ? 0 : 1) as! T
            }else if let value = try? container.decode(Double.self){
                T.defaultValue = Int(value) as! T
            }else if let value = try? container.decode(Float.self){
                T.defaultValue = Int(value) as! T
            }else if let value = try? container.decode(String.self){
                T.defaultValue = String(value) as! T
            }
        }else if T.defaultValue is Float{
            if let value  = try? container.decode(Int.self){
                T.defaultValue = Float(value) as! T
            }else if let value = try? container.decode(Double.self){
                T.defaultValue = Float(value) as! T
            }else if let value = try? container.decode(Float.self){
                T.defaultValue = Float(value) as! T
            }
            else if let value = try? container.decode(String.self){
                T.defaultValue = String(value) as! T
            }
        }else if T.defaultValue is CGFloat{
            if let value  = try? container.decode(Int.self){
                T.defaultValue = CGFloat(value) as! T
            }else if let value = try? container.decode(Double.self){
                T.defaultValue = CGFloat(value) as! T
            }else if let value = try? container.decode(Float.self){
                T.defaultValue = CGFloat(value) as! T
            }
            else if let value = try? container.decode(String.self){
                let double = Double(value)
                T.defaultValue = CGFloat(double ?? 0.00) as! T
            }
        }
        else if T.defaultValue is Bool{
            if let value  = try? container.decode(Int.self){
                T.defaultValue = (value == 0 ? false : true) as! T
            }
            else if let value = try? container.decode(String.self){
                T.defaultValue = (value == "0" ? false : true) as! T
            }
        }
        wrappedValue = (try? container.decode(T.self)) ?? T.defaultValue
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        try container.encode(wrappedValue)
    }
}


extension KeyedDecodingContainer {
    func decode(_ type: Default.Type, forKey key: KeyedDecodingContainer.Key) throws -> Default where T : DefaultCodable {
        try decodeIfPresent(type, forKey: key) ?? Default(wrappedValue: T.defaultValue)
    }
}


extension UnkeyedDecodingContainer {
    mutating func decode(_ type: Default.Type) throws -> Default where T : DefaultCodable {
        try decodeIfPresent(type) ?? Default(wrappedValue: T.defaultValue)
    }
    
}
struct Person:Codable{
    @Default  var p:String
}


struct Student:Codable{
    @Default  var name:String
    var p:[Person]
    
}

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

你可能感兴趣的:(Swift学习之Codable)