美文网首页iOS开发深度好文
iOS Swift4.0 Codable协议:JSON和模型的转

iOS Swift4.0 Codable协议:JSON和模型的转

作者: 小白进城 | 来源:发表于2018-09-17 13:06 被阅读9次

    简单说明

    在OC中,以及Swift4.0之前,系统一直没有一套数据解析的方法。在Swift4.0后,终于推出了Codable协议,可实现json数据和数据模型的相互转换。

    首先来看下 Codable ,它其实是一个组合协议,由 DecodableEncodable 两个协议组成。

    /// A type that can convert itself into and out of an external representation.
    public typealias Codable = Decodable & Encodable
    
    /// A type that can encode itself to an external representation.
    public protocol Encodable {
        public func encode(to encoder: Encoder) throws
    }
    
    /// A type that can decode itself from an external representation.
    public protocol Decodable {
        public init(from decoder: Decoder) throws
    }
    

    DecodableEncodable 分别是用来实现数据模型的解档和归档。

    数据模型只要遵循了 Codable 协议,就可以方便的进行 JSON 数据和数据模型的相互转换。

    使用介绍

    **JSON 转 模型 **

    核心代码:

    JSONDecoder().decode(type: '某类型', from: 'Data数据')
    

    例如我们有一个个人信息的 JSON 数据,我们想要将其转换为 Person 数据模型。

    let jsonString =
    """
    {
        "name":"LOLITA0164",
        "age":26,
        "address":"fuzhou"
    }
    """
    

    数据模型:

    /// Persion模型,遵循 Codable 协议
    class Person: Codable {
        var name: String?
        var age: Int?
        var address: String?
    }
    

    转换过程:

    // 将 json 字符串转为 data 类型
    if let jsonData = jsonString.data(using: String.Encoding.utf8) {
        if let person = try? JSONDecoder().decode(Person.self, from: jsonData){
            // 转换成功,我们将数据输出
            print(person.name!,person.age!,person.address!)
        }
    }
    

    输出结果:

    LOLITA0164 26 fuzhou
    

    原理

    一旦数据模型遵循了 Codable 协议,编译器自动会生成相关编码和解码的实现。
    该协议中还有一个叫 CodingKey 的协议,用来表示编码和解码的key。

    protocol CodingKey {
        var stringValue: String { get }
        init?(stringValue: String)
        var intValue: Int? { get }
        public init?(intValue: Int)
    }
    

    EncoderDecoder 是编码器和解码器,类似 OC 中的NSCoder。他们完成了数据的编码和解码工作。

    当我们的模型遵循 Codable 时,编译器实际上帮我们完成了下面的工作:

    /// Persion模型,遵循 Codable 协议
    class Person: Codable {
        var name: String?
        var age: Int?
        var address: String?
        // 编码和解码的所对应的 key,编译器会自动生成成员变量的枚举形式
        private enum CodingKeys: String, CodingKey {
            case name = "name"
            case age = "age"
            case address = "address"
        }
        // 解码:JSON -> Model 必须实现这个方法
        required init(from decoder: Decoder) throws {
            // 解码器提供了一个容器,用来存储这些变量
            let container = try decoder.container(keyedBy: CodingKeys.self)
            name = try container.decode(String.self, forKey: .name)
            age = try container.decode(Int.self, forKey: .age)
            address = try container.decode(String.self, forKey: .address)
        }
        // 编码:Model -> JSON 必须实现这个方法
        func encode(to encoder: Encoder) throws {
            // 编码器同样提供了一个容器,用来提供对应变量的值
            var container = encoder.container(keyedBy: CodingKeys.self)
            try container.encode(name, forKey: .name)
            try container.encode(age, forKey: .age)
            try container.encode(address, forKey: .address)
        }
    }
    

    上述是编译器自动帮我们完成遵循 Codable 协议的数据模型的编码和解码过程,这些细节部分一般不需要我们关注。但是,在有些情况下,则需要我们自行实现相应的方法。

    • 数据源和模型的成员变量不一致

    在实际开发过程中,经常遇到数据源和模型的成员变量不一致的情况,这种情况的出现通常是服务端和客户端未达成统一,各自有不同的想法,又或者是开发的顺序不一致,客户端先于服务端完成导致字段不统一。无论那种情况,谁去做改动都是不合理的,那么当客户端想做兼容时,就需要从 CodingKey 协议入手了。

    例如服务端给了我们下面一串数据:

    sonString =
    """
    {
        "NAME":"LOLITA0164",
        "AGE":26,
        "ADDRESS":"fuzhou"
    }
    """
    

    我们的数据模型依旧不变,这时我们调整一下 CodingKey

    /// Persion模型,遵循 Codable 协议
    class Person: Codable {
        var name: String?
        var age: Int?
        var address: String?
        
        /*
        注:
        1、一旦写了CodingKey,需要将所有的成员都列出来(除非你只想解析其中部分字段),并且不能重复。
        2、CodingKeys是固定的枚举的名称,不能自定义。
         */
        private enum CodingKeys: String, CodingKey {
            case name = "NAME"
            case age = "AGE"
            case address = "ADDRESS"
        }
    }
    

    这样,我们就可以正常解析 JSON 数据了。

    • 派生类

    首先看个例子:

    class Dog: Codable {
        var name: String?
    }
    
    class GoldenRetriever: Dog {
        var age: Float?
    }
    

    派生类的数据解析:

    let jsonString =
    """
    {
        "name":"kitty",
        "age":2.5,
    }
    """
    if let jsonData = jsonString.data(using: String.Encoding.utf8) {
        if let dog = try? JSONDecoder().decode(GoldenRetriever.self, from: jsonData){
            dump(dog)
        }
    }
    

    结果:

    ▿ JSONToModelSwift.GoldenRetriever #0
      ▿ super: JSONToModelSwift.Dog
        ▿ name: Optional("kitty")
          - some: "kitty"
      - age: nil
    

    我们发现,GoldenRetriever 类的实例只解析出了父类中的 name 字段,而本类中的 age 未能解析。这说明,Codable 在继承中是无效的,当你在派生类中声明遵循该协议时,则会报错:

    Redundant conformance of 'GoldenRetriever' to protocol 'Decodable'
    Redundant conformance of 'GoldenRetriever' to protocol 'Encodable'
    

    这时候,就需要我们自行实现 Codable 协议了。

    class Dog: Codable {
        var name: String?
    }
    
    class GoldenRetriever: Dog {
        var age: Float?
        
        private enum CodingKeys: String, CodingKey {
            case name
            case age
        }
        // 这里只实现了解码,需要编删除线格式  码时,请自行参考之前的例子
        required init(from decoder: Decoder) throws {
            super.init()
            let container = try decoder.container(keyedBy: CodingKeys.self)
            name = try container.decode(String.self, forKey: .name)
            age = try container.decode(Float.self, forKey: .age)
        }
    }
    

    结果:

    ▿ JSONToModelSwift.GoldenRetriever #0
      ▿ super: JSONToModelSwift.Dog
        ▿ name: Optional("kitty")
          - some: "kitty"
      ▿ age: Optional(2.5)
        - some: 2.5
    

    模型 转 JSON

    核心代码:

    JSONEncoder().encode('遵循 Encodable 的对象')
    

    当我们某个遵循 Codable 协议的对象想要转为 JOSN 数据时,我们则可以借助 JSONEncoder 编码器来实现。

    let p = Person()
    p.name = "LOLITA0164"
    p.age = 26
    p.address = "fuzhou"
    if let jsonData = try? JSONEncoder().encode(p) {
        // 编码成功,将 jsonData 转为字符输出查看
        if let jsonString = String.init(data: jsonData, encoding: String.Encoding.utf8) {
            print("jsonString:" + "\(jsonString)")
        }
    }
    

    输出结果:

    jsonString:{"name":"LOLITA0164","age":26,"address":"fuzhou"}
    

    JSON 转 复杂数据模型

    实际上,除了简单的数据模型,Codable 协议是能够完成嵌套数据模型的转换的。需要注意的是,嵌套的数据模型以及嵌套的子模型都必须遵循 Codable 协议。下面举个例子来说明。

    假如我们有一个关于部门的数据模型,部门中有成员若干,可拥有管理者一名,其中的每一个人可能养了一只宠物狗。数据模型组成如下:

    /// Department模型,也遵循 Codable 协议
    class Department: Codable {
        var name: String
        var id: Int
        var members: [Person] = []
        var manager: Person?
    }
    
    /// Persion模型,遵循 Codable 协议
    class Person: Codable {
        var name: String?
        var age: Int?
        var address: String?
        var aDog:Dog?
        private enum CodingKeys: String, CodingKey {
            case name = "NAME"
            case age = "AGE"
            case address = "ADDRESS"
            case aDog = "dog"
        }
    }
    
    /// Dog模型
    class Dog: Codable {
        var name: String?
    }
    

    解析复杂数据模型

    let jsonString =
        """
        {
            "name":"技术部",
            "id":123,
            "members":[
                {
                    "NAME":"xiaoming",
                    "AGE":24,
                    "ADDRESS":"nanjing",
                    "dog":{
                        "name":"Tom"
                    }
                },
                {
                    "NAME":"LOLITA0164",
                    "AGE":26,
                    "ADDRESS":"nanjing",
                    "dog":{
                        "name":"Tonny"
                    }
                },
            ],
            "manager":{
                "NAME":"ZHANG",
                "AGE":33,
                "ADDRESS":"nanjing",
            }
        }
        """
    if let jsonData = jsonString.data(using: String.Encoding.utf8) {
        if let group = try? JSONDecoder().decode(Department.self, from: jsonData) {
            dump(group)
        }
    }
    

    结果:

    ▿ JSONToModelSwift.Department #0
      ▿ name: Optional("技术部")
        - some: "技术部"
      ▿ id: Optional(123)
        - some: 123
      ▿ members: 2 elements
        ▿ JSONToModelSwift.Person #1
          ▿ name: Optional("xiaoming")
            - some: "xiaoming"
          ▿ age: Optional(24)
            - some: 24
          ▿ address: Optional("nanjing")
            - some: "nanjing"
          ▿ aDog: Optional(JSONToModelSwift.Dog)
            ▿ some: JSONToModelSwift.Dog #2
              ▿ name: Optional("Tom")
                - some: "Tom"
        ▿ JSONToModelSwift.Person #3
          ▿ name: Optional("LOLITA0164")
            - some: "LOLITA0164"
          ▿ age: Optional(26)
            - some: 26
          ▿ address: Optional("nanjing")
            - some: "nanjing"
          ▿ aDog: Optional(JSONToModelSwift.Dog)
            ▿ some: JSONToModelSwift.Dog #4
              ▿ name: Optional("Tonny")
                - some: "Tonny"
      ▿ manager: Optional(JSONToModelSwift.Person)
        ▿ some: JSONToModelSwift.Person #5
          ▿ name: Optional("ZHANG")
            - some: "ZHANG"
          ▿ age: Optional(33)
            - some: 33
          ▿ address: Optional("nanjing")
            - some: "nanjing"
          - aDog: nil
    

    我们可以看到,从使用上,无论解析简单的数据模型还是复杂的嵌套模型,在 JSON 转 Model 的使用方面都是一样的,实际上,Model 转 JSON 也是一致的,大家可以尝试一下。


    问题和改进

    虽然自定义 CodingKey 可以完成数据源和数据模型不一致的问题(这和 OC 下的一些数据模型转换采用的方式非常相似),但是在实际情况下,我们经常遇到:数据模型相同,数据来源却可能不一致,这导致一套 CodingKey 无法完成多种不同的编码和解码。那么一定要提前完成映射吗?能否在拿到数据之后,进行一次加工,将数据源处理成完全符合我们数据模型的标准再进行数据转换呢?答案是肯定的。
    在 OC 的数据模型转换中,笔者通过 runtime 和 KVC 方式给数据模型赋值,以达到数据转模型的目的,其中,映射字典是其中关键的一环,目的就是通过映射字典将数据处理成标准的可直接 KVC 赋值的数据,以此将数据转模型变得更灵活。

    我们先看下使用过程:

    字典 转 简单数据模型

    首先依旧是 Person 类 和其数据源

    /// Persion模型,遵循 Codable 协议
    class Person: Codable {
        var name: String?
        var age: Int?
        var address: String?
    }
    // 数据字典
    let dic_p:[String:Any] = [
        "Name":"LOLITA0164",
        "Age":26,
        "address":"fuzhou",
    ]
    

    使用:

    // 映射字典
    // '模型字段':'数据源字段'
    let dic_hint = [
        "name":"Name",
        "age":"Age"
    ]
    // 转换
    if let p = try? LLModelTool.decode(Person.self, resDic: dic_p, hintDic: dic_hint) {
        dump(p)
    }
    

    结果:

    ▿ JSONToModelSwift.Person #0
      ▿ name: Optional("LOLITA0164")
        - some: "LOLITA0164"
      ▿ age: Optional(26)
        - some: 26
      ▿ address: Optional("fuzhou")
        - some: "fuzhou"
    

    字典 转 嵌套数据模型

    依旧是上面的例子:假如我们有一个关于部门的数据模型,部门中有成员若干,可拥有管理者一名,其中的每一个人可能有养一只宠物狗。

    /// Department模型,也遵循 Codable 协议
    class Department: Codable {
        var name: String?
        var id: Int?
        var members: [Person] = []
        var manager: Person?
    }
    
    /// Persion模型,遵循 Codable 协议
    class Person: Codable {
        var name: String?
        var age: Int?
        var address: String?
        var aDog:Dog?
    }
    
    /// Dog模型
    class Dog: Codable {
        var name: String?
    }
    
    
    // 数据源
    let dic_group: [String:Any] = [
        "NAME":"技术部",
        "ID":123,
        "MEMBERS":[
            [
                "Name":"小熊",
                "Age":25,
                "Address":"南京",
                "Dog":[
                    "NameString":"kitty"
                ],
            ],
            [
                "Name":"LOLITA0164",
                "Age":26,
                "Address":"fuzhou"
            ]
        ],
        "Manager":[
            "name":"管理者",
            "age":33
        ]
    ]
    

    使用:

    // 映射字典
    // '模型字段':'数据源字段'
    let dic_hint2: [String:Any] = [
        // Department数据模型的映射关系
        "name":"NAME",
        "id":"ID",
        "members":"MEMBERS",
        // 嵌套模型的映射关系(key 对应数据源中的 key)
        "MEMBERS":[
            // Person数据模型的映射关系
            "name":"Name",
            "age":"Age",
            "address":"Address",
            "aDog":"Dog",
            // 嵌套模型的映射关系(key 对应数据源中的 key)
            "Dog":[
                // Dog数据模型的映射关系
                "name":"NameString"
            ]
        ],
        "manager":"Manager"
    ]
    
    if let group = try? LLModelTool.decode(Department.self, resDic: dic_group, hintDic: dic_hint2) {
        dump(group)
    }
    

    结果:

    ▿ JSONToModelSwift.Department #0
      ▿ name: Optional("技术部")
        - some: "技术部"
      ▿ id: Optional(123)
        - some: 123
      ▿ members: 2 elements
        ▿ JSONToModelSwift.Person #1
          ▿ name: Optional("小熊")
            - some: "小熊"
          ▿ age: Optional(25)
            - some: 25
          ▿ address: Optional("南京")
            - some: "南京"
          ▿ aDog: Optional(JSONToModelSwift.Dog)
            ▿ some: JSONToModelSwift.Dog #2
              ▿ name: Optional("kitty")
                - some: "kitty"
        ▿ JSONToModelSwift.Person #3
          ▿ name: Optional("LOLITA0164")
            - some: "LOLITA0164"
          ▿ age: Optional(26)
            - some: 26
          ▿ address: Optional("fuzhou")
            - some: "fuzhou"
          - aDog: nil
      ▿ manager: Optional(JSONToModelSwift.Person)
        ▿ some: JSONToModelSwift.Person #4
          ▿ name: Optional("管理者")
            - some: "管理者"
          ▿ age: Optional(33)
            - some: 33
          - address: nil
          - aDog: nil
    

    注:如果只有少数部分是不统一的,我们也可以通过 CodingKey 将部分统一的字段编写对应关系,少数部分通过映射字典更换资源字典数据,以完成转换。

    例如:

    /// Persion模型,遵循 Codable 协议
    class Person: Codable {
        var name: String?
        var age: Int?
        var address: String?
        // CodingKeys 只有两个映射枚举
        private enum CodingKeys: String, CodingKey {
            case name = "NAME"
            case age = "AGE"
            case address
        }
    }
    
    // 源字典中有第三个字段和 CodingKeys 中的不一致
    let dic_p:[String:Any] = [
        "NAME":"LOLITA0164",
        "AGE":26,
        "ADDRESS":"fuzhou",
    ]
    // 映射字典,只需映射不一致的即可
    let dic_hint = [
        "address":"ADDRESS",
    ]
    if let p = try? LLModelTool.decode(Person.self, resDic: dic_p, hintDic: dic_hint) {
        dump(p)
    }
    

    实现过程

    首先,我们将 JSONDecoder().decode()进行再次封装:

    /// 字典 转 模型
    static func decode<T>(_ type: T.Type, resDic: [String:Any] , hintDic:[String:Any]?) throws -> T where T: Decodable {
        var transformDic = resDic
        if (hintDic != nil) {
            // 将映射字典转换成模型所需的字典
            transformDic = self.setUpResourceDic(resDic: resDic, hintDic: hintDic!)
        }
        guard let jsonData = self.getJsonData(param: transformDic) else {
            throw LLModelToolError.message("转成 Data 时出错!!!")
        }
        guard let model = try? JSONDecoder().decode(type, from: jsonData)
            else {
            throw LLModelToolError.message("转成 数据模型 时出错!!!")
        }
        return model
    }
    

    我们可以看到,该方法的核心依旧是系统的转换方法,我们要做的就是将映射字典转换成模型所需的字典,然后的处理一切照旧。

    核心的转换方法如下:

    /// 根据映射字典设置当前字典内容
    private static func setUpResourceDic(resDic: [String:Any] , hintDic:[String:Any]) -> [String:Any]{
        var transformDic = resDic
        for (key,value) in hintDic {
            let valueNew: AnyObject = value as AnyObject
            if valueNew.classForCoder == NSDictionary.classForCoder(){      // 模型映射
                let res_value = resDic[key] as AnyObject    // 为了获取数据类型
                if res_value.classForCoder == NSArray.classForCoder(){  // 数据类型为数组(模型数组)
                    let res_value_array = res_value as! [[String:Any]]
                    var resArray: [Any] = []
                    for item in res_value_array {
                        // 递归调用,寻找子模型
                        let res = self.setUpResourceDic(resDic: item , hintDic: valueNew as! [String : Any])
                        resArray.append(res)
                    }
                    let realKey = self.getRealKey(key: key, dic: hintDic)
                    transformDic[realKey] = resArray
                    // 移除旧的数据
                    if realKey != key {
                        transformDic.removeValue(forKey: key)
                    }
                }
                else if res_value.classForCoder == NSDictionary.classForCoder(){    // 数据类型为字典(模型)
                    // 递归调用,寻找子模型
                    let res = self.setUpResourceDic(resDic: res_value as! [String : Any] , hintDic: valueNew as! [String : Any])
                    let realKey = self.getRealKey(key: key, dic: hintDic)
                    transformDic[realKey] = res
                    // 移除旧的数据
                    if realKey != key {
                        transformDic.removeValue(forKey: key)
                    }
                }
            }else if valueNew.classForCoder == NSString.classForCoder(){    // 普通映射
                // 去掉
                if !hintDic.keys.contains(valueNew as! String){
                    transformDic[key] = resDic[valueNew as! String]
                }
                // 移除旧的数据
                if key != valueNew as! String {
                    transformDic.removeValue(forKey: valueNew as! String)
                }
            }
        }
        return transformDic
    }
    

    转换的思路为:

    1、中心思想无非就是进行 key 的替换

    2、遍历映射字典,如果映射字典中是 "String":"String" 我们直接进行替换(先新增数据,再将就数据删除),如果是 "String":"Dictionary" ,则表示该字段中的 Dictionary 是一个数据模型,此时我们需要取出该字典,采用递归的方式深层次的寻找和替换。

    缺点建议

    复杂的数据模型在使用起来不是非常的顺手,因为我们需要为其集中编写复杂的对应关系,因此不如将数据拆成简单的数据模型,再赋值给复杂模型,这样映射字典变得简单很多,也更易阅读。

    完整的代码为:

    import Foundation
    
    enum LLModelToolError: Error {
        case message(String)
    }
    
    struct LLModelTool {
        
        /// 字典 转 模型
        static func decode<T>(_ type: T.Type, resDic: [String:Any] , hintDic:[String:Any]?) throws -> T where T: Decodable {
            // 将映射字典转换成模型所需的字典
            var transformDic = resDic
            if (hintDic != nil) {
                transformDic = self.setUpResourceDic(resDic: resDic, hintDic: hintDic!)
            }
            guard let jsonData = self.getJsonData(param: transformDic) else {
                throw LLModelToolError.message("转成 Data 时出错!!!")
            }
            guard let model = try? JSONDecoder().decode(type, from: jsonData)
                else {
                throw LLModelToolError.message("转成 数据模型 时出错!!!")
            }
            return model
        }
        
        
        /// json 转模型
        static func decode<T>(_ type: T.Type, jsonData: Data , hintDic:[String:Any]?) throws -> T where T: Decodable {
            guard let resDic: [String:Any] = try? JSONSerialization.jsonObject(with: jsonData, options: JSONSerialization.ReadingOptions.mutableContainers) as! [String : Any] else {
                throw LLModelToolError.message("转成 字典 时出错!!!")
            }
            return try! self.decode(type, resDic: resDic, hintDic: hintDic)
        }
        
        // 模型转字典
        static func reflectToDict<T>(model: T) -> [String:Any] {
            let mirro = Mirror(reflecting: model)
            var dict = [String:Any]()
            for case let (key?, value) in mirro.children {
                dict[key] = value
            }
            return dict
        }
        
        
        
        /// 获取 json 数据,data类型
        static func getJsonData(param: Any) -> Data? {
            if !JSONSerialization.isValidJSONObject(param) {
                return nil
            }
            guard let data = try? JSONSerialization.data(withJSONObject: param, options: []) else {
                return nil
            }
            return data
        }
    
    
        /// 根据映射字典设置当前字典内容
        private static func setUpResourceDic(resDic: [String:Any] , hintDic:[String:Any]) -> [String:Any]{
            var transformDic = resDic
            for (key,value) in hintDic {
                let valueNew: AnyObject = value as AnyObject
                if valueNew.classForCoder == NSDictionary.classForCoder(){      // 模型映射
                    let res_value = resDic[key] as AnyObject    // 为了获取数据类型
                    if res_value.classForCoder == NSArray.classForCoder(){  // 数据类型为数组(模型数组)
                        let res_value_array = res_value as! [[String:Any]]
                        var resArray: [Any] = []
                        for item in res_value_array {
                            // 递归调用,寻找子模型
                            let res = self.setUpResourceDic(resDic: item , hintDic: valueNew as! [String : Any])
                            resArray.append(res)
                        }
                        let realKey = self.getRealKey(key: key, dic: hintDic)
                        transformDic[realKey] = resArray
                        // 移除旧的数据
                        if realKey != key {
                            transformDic.removeValue(forKey: key)
                        }
                    }
                    else if res_value.classForCoder == NSDictionary.classForCoder(){    // 数据类型为字典(模型)
                        // 递归调用,寻找子模型
                        let res = self.setUpResourceDic(resDic: res_value as! [String : Any] , hintDic: valueNew as! [String : Any])
                        let realKey = self.getRealKey(key: key, dic: hintDic)
                        transformDic[realKey] = res
                        // 移除旧的数据
                        if realKey != key {
                            transformDic.removeValue(forKey: key)
                        }
                    }
                }else if valueNew.classForCoder == NSString.classForCoder(){    // 普通映射
                    // 去掉
                    if !hintDic.keys.contains(valueNew as! String){
                        transformDic[key] = resDic[valueNew as! String]
                    }
                    // 移除旧的数据
                    if key != valueNew as! String {
                        transformDic.removeValue(forKey: valueNew as! String)
                    }
                }
            }
            return transformDic
        }
        
        
        /// 从映射字典中获取到模型中对应的key
        private static func getRealKey(key:String, dic:[String:Any]) -> String {
            for (k,v) in dic {
                let value: AnyObject = v as AnyObject
                if value.classForCoder == NSString.classForCoder(){
                    let valueNew = value as! String
                    if valueNew == key{
                        return k
                    }
                }
            }
            return key
        }
    
    }
    

    补充说明

    在数据模型中的成员变量中,基本数据类型如:StringIntFloat等都已经实现了 Codable 协议,因此如果你的数据类型只包含这些基本数据类型的属性,只需要在类型声明中加上 Codable 协议就可以了,不需要写任何实际实现的代码。

    但是,一些特殊类型还有有一些限制

    • 枚举

    枚举需要声明原始值的类型,并且声明遵循 Codable 协议。

    enum Sex: String ,Codable {
        case female
        case male
    }
    // 数据模型
    class People: Codable {
        var sex: Sex?
    }
    
    // 数据源
    let dic: [String : Any] = [
        "sex":"male",
    ]
    // 转换
    if let p = try? LLModelTool.decode(People.self, resDic: dic, hintDic: nil) {
        dump(p)
    }
    

    输出:

    ▿ JSONToModelSwift.People #0
      ▿ sex: Optional(JSONToModelSwift.Sex.male)
        - some: JSONToModelSwift.Sex.male
    
    • 布尔型

    Bool 类型默认只支持 true/false 形式的 Bool 值解析。对于一些使用 0/1 形式来表示 Bool 值的后端框架,只能通过 Int 类型解析之后再做转换了,或者可以自定义实现 Codable 协议。

    enum Sex: String ,Codable {
        case female
        case male
    }
    // 数据模型
    class People: Codable {
        var sex: Sex?
        var isTall: Bool? = nil
    }
    
    // 数据源
    let dic: [String : Any] = [
        "sex":"male",
        "isTall":true
    ]
    // 转换
    if let p = try? LLModelTool.decode(People.self, resDic: dic, hintDic: nil) {
        dump(p)
    }
    

    输出:

    ▿ JSONToModelSwift.People #0
      ▿ sex: Optional(JSONToModelSwift.Sex.male)
        - some: JSONToModelSwift.Sex.male
      ▿ isTall: Optional(true)
        - some: true
    

    参考

    1、Swift 4 踩坑之 Codable 协议

    2、Swift 4.0: Codable

    3、Swift 中 class 怎么支持 Codable

    4、swift4 字典->模型-转换

    三方转换库

    这些库我都没有使用过,仅仅是从其他人那边摘抄过来做备份,读者有兴趣可以试一试。

    相关文章

      网友评论

        本文标题:iOS Swift4.0 Codable协议:JSON和模型的转

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