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

简单说明

在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 字典->模型-转换

三方转换库

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

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

推荐阅读更多精彩内容