json转模型之JSONDecode

732 阅读6分钟

前言

JSON转模型,在Object-c中是比较常见,Swift中当然也有,比较常见的一些如下:

ObjectMapper: 面向协议的 Swift JSON 解析框架

HandyJSON: 阿里推出的一个用于 Swift 的 JSON 序列化/反序列化库。

KakaJSON: mj 新写的一个Swift的JSON转模型工具

其中上面的库基本上都是使用 Mirror反射来获取类的信息,以及赋值等(要研究一些源码才能理解赋值过程,,其实要仔细看也不难,可以直接接收大佬们的研究成果🤣,我后面也要看看记录一下),另外纯 Swift的对象已经已经不具备 runtime特性了,只能使用反射

JSONDecode: 为苹果推出的 Swift JSON转model的工具,由于是苹果自家的,也挺好用,这边就主要介绍此库了

测试demo

上面的案例各有各的好处,使用也相对简单,点进去就有介绍

JSONDecode的好处就是不依赖三方库,且使用也很方便

JSONDecode

JSONDecoder使用使用的就是该类,下面定义一个案例JSON来一步一步演示使用它

//定义一个JSON类型也比较多,相对复杂
let baseJSON = """
{
    "id": 123123123,
    "name": "剪刀石头布",
    "age": 29,
    "state": 1,
    "file_info": {
        "pic": "http://123.png",
        "thum_pic": "http://123_thum.png",
    },
    "current_department": {
        "id": 1,
        "name": "数字孪生开发部门",
        "level": 100,
        "count": 20
    },
    "enterprise_info": {
        "id": "saiirower231oi23104004",
        "name": "詹小帅",
        "age": 30,
        "department_id": 1,
        "department": [{
            "id": 1,
            "name": "数字孪生开发部门",
            "level": 100,
            "count": 20
        },{
            "id": 1,
            "name": "小程序开发部门",
            "level": 100,
            "count": 20
        },{
            "id": 1,
            "name": "APP基础开发部门",
            "level": 100,
            "count": 20
        }]
    }
}
"""

简单案例一

使用 JSONDecode 解析的模型,需要遵循 Codable 协议

如下所示,只使用了两个参数,那么就解析两个参数,遵循 Codable 协议

struct SimpleModel: Codable {
    var name: String?
    var age: Int = 0
}

解析案例如下所示,会自动将 json 中指定key与指定对象的参数赋值

解析过程中,失败可能会抛出异常,因此要使用 try-catch 来解决,这样就解析完毕了

let decode = JSONDecoder()
if let data = baseJSON.data(using: .utf8) {
    do {
        let simModel = try decode.decode(SimpleModel.self, from: data)
        self.simpleJSON = simModel
        print("simModel", simModel)
    } catch {
        print("出错了1")
    }
}

下面就成功了,打印出来对象了(Optional为可选类型,即?声明的类型)

image.png

中等案例二

与简单案例不同,这次我们碰到一个特殊的键值id,在项目中我们想给他重命名,映射到对象指定属性上

那么可以使用 CodingKeys 枚举来设置映射关系

struct MiddleModel: Codable {
    var ID: Int = 0
    var name: String?
    var age: Int = 0
    //设置映射关系,一旦设置就要全设置,否则不解析指定键值
    //如果与映射值一样,可以不用赋值,但是得声明出来
    enum CodingKeys: String, CodingKey {
        case ID = "id" //设置了之后,json中的 id 映射到当前对象的ID属性
        case name = "name"
        case age  //如果与映射值一样,可以不用赋值,但是得声明出来
    }
}

如下所示,这样就 JSON 转中等模型成功了

let decode = JSONDecoder()
if let data = baseJSON.data(using: .utf8) {
    do {
        let midModel = try decode.decode(MiddleModel.self, from: data)
        self.middleJSON = midModel
        print("middleModel", midModel)
    } catch {
        print("出错了2")
    }
}

打印结果如下所示 ,多了个 ID

image.png

正常使用中,我们可能会想把 JSON 子对象的值,直接赋值到模型的外层属性上,其他三方转模型可能会有这功能,而这里没有,因此不可以像下面似的解析,需要按照 JOSN 对应结构来转化才行,参考复杂案例解析

//下面是一个 JSONDecode 不支持的案例
struct MiddleModel: Codable {
    //var pic: String? = nil
    //var thumPic: String? = nil
    
    //注意:如上所示,无法将对象内的数据解析映射到对象外面
    //只能按照正常结构解析,可以选择不解析,解析见后面的complex
    enum CodingKeys: String, CodingKey {
        //case pic = "file_info.pic"
        //case thumPic = "file_info.thum_pic"
    }
}

注意: 映射关系一旦设置,就要全部写上,否则不映射

复杂案例三

如下所示,想用到 JSON 的子对象属性,需要将子对象全部解析出来,因此,子对象也要全部声明,且需遵循 Codeabl 协议

使用过程中,如果碰到需要映射的,则此类需要映射全部属性,否则不解析

子属性则根据其自身情况,来是是否需要映射(键值与属性名一致也不需要映射)

//一旦设置了映射,则无法使用 JSONDecode的蛇形转驼峰
//子对象碰到蛇形转模型时,也需要给出映射
struct ComplexModel: Codable {
    var ID: Int = 0 //实际可以用id,这里只是案例
    var name: String?
    var age: Int = 0
    
    var state: State = .normal
    
    var fileInfo: FileInfo?
    var currentDepartment: Department?
    var enterpriseInfo: EnterpriseInfo?
    
    //通过对键值设置映射值,一旦设置就要全部设置,否则不解析
    enum CodingKeys: String, CodingKey {
        case ID = "id"
        case name   //名字一样可以省略后面
        case age
        case state
        case fileInfo = "file_info"
        case currentDepartment = "current_department"
        case enterpriseInfo = "enterprise_info"
    }
    
    enum State: Int, Codable {
        case normal = 0
        case isEnter = 1
        case isLeave = 2
    }
    
    //定义 file_info 指定对象
    struct FileInfo: Codable {
        var pic: String?
        var thumPic: String?
        enum CodingKeys: String, CodingKey {
            case pic
            case thumPic = "thum_pic"
        }
    }

    //定义 department 指定对象
    struct Department: Codable {
        var id: Int = 0
        var name: String?
        var level: Int = 0
        var count: Int = 0
    }
    
    //定义 enterprise_info 指定对象
    struct EnterpriseInfo: Codable {
        var id: String?
        var name: String?
        var age: Int = 0
        var departmentId: Int = 0
        var department: [Department]?
        
        enum CodingKeys: String, CodingKey {
            case id
            case name
            case age
            case departmentId = "department_id"
            case department
        }
    }
}

就这样,使用和前面一样的解析手段,JSON 转复杂模型也成功了,就是对象设置时稍复杂

let decode = JSONDecoder()
        
if let data = baseJSON.data(using: .utf8) {
    do {
        let comModel = try decode.decode(ComplexModel.self, from: data)
        self.complexJSON = comModel
        print("complexModel", comModel)
    } catch {
        print("出错了3")
    }
}

就这样,复杂模型也转化成功了

image.png

复杂案例之自动蛇形转驼峰

看上面的转模型感觉有些复杂,实际使用,可能后台的字段拿来就直接用了,只是可能需要蛇形转驼峰

蛇形命名法:用 _ 作为间隔的键值,例如: user_info

驼峰命名法: 用 首字母大写 作为间隔的键值,例如: userInfo

如下所示,声明一个复杂类型的驼峰命名法模型

//不使用映射时,可以将 JSONDecode 的 keyDecodingStrategy 属性设置为.convertFromSnakeCase
class ComplexAutoModel: Codable {
    var id: Int = 0
    var name: String?
    var age: Int = 0
    
    var state: State = .normal
    
    var fileInfo: FileInfo?
    var currentDepartment: Department?
    var enterpriseInfo: EnterpriseInfo?
    
    enum State: Int, Codable {
        case normal = 0
        case isEnter = 1
        case isLeave = 2
    }
    
    struct FileInfo: Codable {
        var pic: String?
        var thumPic: String?
    }

    struct Department: Codable {
        var id: Int = 0
        var name: String?
        var level: Int = 0
        var count: Int = 0
    }
    
    struct EnterpriseInfo: Codable {
        var id: String?
        var name: String?
        var age: Int = 0
        var departmentId: Int = 0
        var department: [Department]?
    }
}

解析过程,就是额外将 decode对象的 keyDecodingStrategy参数,设置成 .convertFromSnakeCase枚举即可

let decode = JSONDecoder()
//蛇形转驼峰,模型定义不能使用映射,不然会解析失败
decode.keyDecodingStrategy = .convertFromSnakeCase

if let data = baseJSON.data(using: .utf8) {
    do {
        let comAutoModel = try decode.decode(ComplexAutoModel.self, from: data)
        self.complexAutoJSON = comAutoModel
        print("complexAutoModel", comAutoModel)
    } catch {
        print("出错了3")
    }
}

如下所示,解析成功了(这里没打印出来,直接看的对象)

image.png

对象转JSON扩展知识

使用中,难免会碰到对象转JSON,解析JSONData等,下面给出一些案例使用

//模型 转 jsonData
let jsonData = try JSONEncoder().encode(simModel)
//jsonData 转 字典
let jsonDic = try JSONSerialization.jsonObject(with: jsonData, options: .mutableContainers)
//jsonData 转 jsonString
let jsonString = String(data: jsonData, encoding: .utf8)
print("jsonDic", jsonDic)
if let json = jsonString {
    print("jsonString", json)
}

最后

快来试一下吧,其他三方也可以试一试,没准有你喜欢的

另外推荐研究一个 Mirror反射,字典转模型的核心步骤就是这个,反射键值和写入,其他的都是在准备保存等操作

另外,祝大家新年快乐!