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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,723评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,485评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,998评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,323评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,355评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,079评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,389评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,019评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,519评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,971评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,100评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,738评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,293评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,289评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,517评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,547评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,834评论 2 345

推荐阅读更多精彩内容