Codable发布这么久我就不学,摸鱼爽歪歪,哎~就是玩儿
Posted HelloWord杰少
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Codable发布这么久我就不学,摸鱼爽歪歪,哎~就是玩儿相关的知识,希望对你有一定的参考价值。
本文同步发表于我的微信公众号,扫一扫文章底部的二维码或在微信搜索 HelloWorld杰少 即可关注。
写在开头
祝天下所有伟大的母亲:
节日快乐,身体健康, 感谢您的付出,感谢您的仁爱!
---- 写在母亲节当天
前言
对于大多数的应用程序来说,最常见的任务就是进行网络数据的发送和接收,但是在执行此操作之前,我们需要通过编码或者序列化的方式将数据转换为合适的格式来发送,然后还需要将收到的网络数据转换为合适的格式,这样才能在应用中使用它们,这样的过程叫做解码或着叫反序列化。
那如何去定义这个格式呢!这里就不得不提 JSON 了,JSON 目前是网络通信发送和接收数据最常用的格式,但是在 Swift4.0 之前,大家都是用一些第三方的开源库来对 JSON 格式进行解析。
终于, Apple 在 Swift4.0 的 Foundtion 模块中添加了对 JSON 解析的原生支持,它的功能强大而且易于使用,接下来就让我带大家
了解下在 swift 里如何来对你的数据进行 encoding 和 decoding 吧!
基础知识介绍
在 swift 里要对 JSON 进行处理的话,首先需要了解的概念就是:Codable,
Codable 其实它不是一个协议,而是另外俩个协议的组合:Decodable 和 Encodable,它的源码如下所示:
public typealias Codable = Decodable & Encodable
所以聪明的你一定可以猜到,只要数据模型遵行了 Codable 协议,那么就可以方便的进行 JSON 和数据模型的相互转换了。
在 Swift4.0 中,Apple 提供了 JSONEncoder 和 JSONDecoder 俩对象来处理 JSON 的编码和解码,核心代码如下:
let encoder = JSONEncoder()
let decoder = JSONDecoder()
相关的概念已介绍完毕,你准备好迎接挑战了吗?
JSON 转数据模型
TASK 1:简单的数据结构
如果你的 JSON 结构和你使用的数据模型结构一致的话,那么解析过程将会非常简单,请看下面内容:
下面给出的是一个歌曲的 JSON 数据,我现在要将其转换为 SongModel。
let song = """
{
"singer": "The Chainsmokers",
"name": "Something Just Like This",
}
"""
SongModel
/// SongModel模型,遵循 Codable 协议
struct SongModel: Codable {
var singer: String?
var name: String?
}
转换过程如下:
if let jsonData = song.data(using: String.Encoding.utf8) {
if let sSong = try? JSONDecoder().decode(SongModel.self, from: jsonData) {
dump(sSong)
}
}
输出结果如下:
▿ JSONDecoderDemo.SongModel
▿ singer: Optional("The Chainsmokers")
- some: "The Chainsmokers"
▿ name: Optional("Something Just Like This")
- some: "Something Just Like This"
这样就完成了解析,是不是很简单!
NOTE:在数据模型的成员变量中,基本数据类型如:String、Int、Float等都已经实现了 Codable 协议,因此如果你的数据类型只包含这些基本数据类型的属性,只需要在类型声明中加上 Codable 协议就可以了,不需要写任何实际实现的代码。
TASK 2: 解析数组
假如这是我们收到的一张专辑 Album 的 JSON 数据,现在要把它转化成 AlbumModel 数据模型。
let album = """
{
"singer": "The Chainsmokers",
"name": "Something Just Like This",
"songs":[
"Something Just Like This",
"Closer",
"Young",
"All We Know"
]
}
"""
AlbumModel
struct AlbumModel: Codable {
var singer: String?
var name: String?
var songs: [String]?
}
转换过程如下:
if let jsonData = album.data(using: String.Encoding.utf8) {
if let sSong = try? JSONDecoder().decode(AlbumModel.self, from: jsonData) {
dump(sSong)
}
}
输出结果为:
▿ JSONDecoderDemo.AlbumModel
▿ singer: Optional("The Chainsmokers")
- some: "The Chainsmokers"
▿ name: Optional("Something Just Like This")
- some: "Something Just Like This"
▿ songs: Optional(["Something Just Like This", "Closer", "Young", "All We Know"])
▿ some: 4 elements
- "Something Just Like This"
- "Closer"
- "Young"
- "All We Know"
和上面的转换如出一辙,想必你现在心里已经在默默的嘀咕:这么简单还用你讲?
那接下来就请准备迎接新的挑战把!
TASK 3:结构不一致
上面所演示的 JSON 数据格式都是与数据模型里的成员变量一一对应的,但是,在实际开发中,你会经常遇到数据源的格式和数据模型结构
不一致的情况,很多情况下可能是服务端与客户端没有统一好接口的格式,然后各自就开始开发,到需要进行调试的时候,客户端一收到消息,就懵逼了:
NOTE: 所以在这里我非常建议大家在做功能开发之前一定要先把接口文档定义好,定义好,定义好,重要的事情讲三遍。
这样服务端和客户端之间定义的数据格式就存在了差异,无论怎样当然总有一方需要作出让步来做到兼容,那么当客户端想要做兼容时,该怎么处理呢!我们先来看个例子:
例如服务端返回的数据为:
let album = """
{
"singer": "The Chainsmokers",
"name": "Something Just Like This",
"songList":[
"Something Just Like This",
"Closer",
"Young",
"All We Know"
]
}
"""
可以看到,原来的 songs 换成了 songList,我们执行下原先的代码,看下输出:
▿ JSONDecoderDemo.AlbumModel
▿ singer: Optional("The Chainsmokers")
- some: "The Chainsmokers"
▿ name: Optional("Something Just Like This")
- some: "Something Just Like This"
- songs: nil
发现 songs 字段变成了 nil, 这个解析就失败了,那如何做到不修改我之前定义的数据模型的成员变量,来做到兼容呢!这时候就需要用到 CodingKey 协议了,
借助 CodingKey 可以用来映射数据模型的成员变量,首先在数据模型中添加一个特殊的枚举类型:
private enum CodingKeys: String, CodingKey
添加完后的数据模型代码如下:
struct AlbumModel: Codable {
var singer: String?
var name: String?
var songs: [String]?
enum CodingKeys: String, CodingKey {
case singer = "singer"
case name = "name"
case songs = "songList"
}
}
输出结果为:
▿ JSONDecoderDemo.AlbumModel
▿ singer: Optional("The Chainsmokers")
- some: "The Chainsmokers"
▿ name: Optional("Something Just Like This")
- some: "Something Just Like This"
▿ songs: Optional(["Something Just Like This", "Closer", "Young", "All We Know"])
▿ some: 4 elements
- "Something Just Like This"
- "Closer"
- "Young"
- "All We Know"
这样,我们是不是就可以正常解析 JSON 数据了,是不是很强大。
接下来再增加一个难度!
当给你的唱片的 JSON 结构是这样的,你该怎么解析呢!
let album = """
{
"singer": "The Chainsmokers",
"name": "Something Just Like This",
"songs": "Something Just Like This"
}
"""
根据上面给出的例子,你会发现它依然与你的数据模型不匹配,原来的 songs 字段不是数组格式了,那如何才能正常的解析到数据模型上去呢,这时候就需要我们自己来实现编码解码的逻辑了。
首先在 AlbumModel 中加入以下代码:
struct AlbumModel: Codable {
var singer: String?
var name: String?
var songs: [String]?
// 1
enum CodingKeys: String, CodingKey {
case singer = "singer"
case name = "name"
case songs = "songs"
}
// 解码: JSON 转 Model
init(from decoder: Decoder) throws {
// 2
let container = try decoder.container(keyedBy: CodingKeys.self)
// 3
singer = try container.decode(String.self, forKey: .singer)
name = try container.decode(String.self, forKey: .name)
let ss = try container.decode(String.self, forKey: .songs)
songs = [ss]
}
// 编码: Model 转 JSON
func encode(to encoder: Encoder) throws {
// 4
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(singer, forKey: .singer)
try container.encode(name, forKey: .name)
try container.encode(songs, forKey: .songs)
}
}
解释如下:
- 创建 CodingKeys 枚举,用于映射 JSON 字段。
- 创建一个解码器容器,来存储 JSON 里的属性。
- 使用适当的类型和编码键从容器中提取歌手和专辑名和歌单,由于歌单是数组类型的,所以需要将提取到的歌转换成数组。
- 创建 KeyedEncodingContainer 容器来对数据模型里的属性进行编码。
转换过程如下:
if let jsonData = album.data(using: String.Encoding.utf8) {
if let aAlbum = try? JSONDecoder().decode(AlbumModel.self, from: jsonData) {
dump(aAlbum)
}
}
结果如下:
▿ JSONDecoderDemo.AlbumModel
▿ singer: Optional("The Chainsmokers")
- some: "The Chainsmokers"
▿ name: Optional("Something Just Like This")
- some: "Something Just Like This"
▿ songs: Optional(["Something Just Like This"])
▿ some: 1 element
- "Something Just Like This"
可以看到通过上面的代码,已经可以将 JSON 中原先的 String 转换成数据模型中的数组类型了。
注意:如果需要借助 CodingKeys 解决字段不一致的情况,即使其他的属性不需要映射,也必须将其包含在枚举中,譬如:singer, name,否则会报错。
TASK 4: 复杂的嵌套
除了处理简单的数据模型,Codable 还可以处理复杂的嵌套数据模型,首先解释下什么是嵌套数据模型:
譬如我有个专门处理专辑的数据模型叫 AlbumModel,它里面内嵌了 SongModel 的属性,这就是嵌套。这里必须要说明的就是嵌套的数据模型以及嵌套的子模型都必须遵循 Codable 协议,下面我们举个嵌套的数据模型的例子来说明一下:
/// 专辑模型
struct AlbumModel: Codable {
// 专辑名
var albumName: String?
// 发布时间
var releaseTime: String?
// 歌单(嵌套)
var songs: [SongModel]?
}
/// 歌曲模型
struct SongModel: Codable {
// 歌手(嵌套)
var singer: SingerModel?
// 歌曲
var name: String?
}
/// 歌手模型
struct SingerModel: Codable {
// 姓名
var name: String?
// 年龄
var age: Int?
}
JSON 数据结构
let album = """
{
"albumName": "Something Just Like This",
"releaseTime": "2017-02-22",
"songs":[
{
"singer": {
"name":"The Chainsmokers",
"age": 30
},
"name": "Something Just Like This"
},
{
"singer": {
"name":"The Chainsmokers",
"age": 30
},
"name": "Closer"
},
{
"singer": {
"name":"The Chainsmokers",
"age": 30
},
"name": "Young"
},
{
"singer": {
"name":"The Chainsmokers",
"age": 30
},
"name": "All We Know"
}
]
}
"""
转换过程如下:
if let jsonData = album.data(using: String.Encoding.utf8) {
if let aAlbum = try? JSONDecoder().decode(AlbumModel.self, from: jsonData) {
dump(aAlbum)
}
}
输出结果为:
JSONDecoderDemo.AlbumModel
▿ albumName: Optional("Something Just Like This")
- some: "Something Just Like This"
▿ releaseTime: Optional("2017-02-22")
- some: "2017-02-22"
▿ songs: Optional([JSONDecoderDemo.SongModel(singer: Optional(JSONDecoderDemo.SingerModel(name: Optional("The Chainsmokers"), age: Optional(30))), name: Optional("Something Just Like This")), JSONDecoderDemo.SongModel(singer: Optional(JSONDecoderDemo.SingerModel(name: Optional("The Chainsmokers"), age: Optional(30))), name: Optional("Closer")), JSONDecoderDemo.SongModel(singer: Optional(JSONDecoderDemo.SingerModel(name: Optional("The Chainsmokers"), age: Optional(30))), name: Optional("Young")), JSONDecoderDemo.SongModel(singer: Optional(JSONDecoderDemo.SingerModel(name: Optional("The Chainsmokers"), age: Optional(30))), name: Optional("All We Know"))])
▿ some: 4 elements
▿ JSONDecoderDemo.SongModel
▿ singer: Optional(JSONDecoderDemo.SingerModel(name: Optional("The Chainsmokers"), age: Optional(30)))
▿ some: JSONDecoderDemo.SingerModel
▿ name: Optional("The Chainsmokers")
- some: "The Chainsmokers"
▿ age: Optional(30)
- some: 30
▿ name: Optional("Something Just Like This")
- some: "Something Just Like This"
▿ JSONDecoderDemo.SongModel
▿ singer: Optional(JSONDecoderDemo.SingerModel(name: Optional("The Chainsmokers"), age: Optional(30)))
▿ some: JSONDecoderDemo.SingerModel
▿ name: Optional("The Chainsmokers")
- some: "The Chainsmokers"
▿ age: Optional(30)
- some: 30
▿ name: Optional("Closer")
- some: "Closer"
▿ JSONDecoderDemo.SongModel
▿ singer: Optional(JSONDecoderDemo.SingerModel(name: Optional("The Chainsmokers"), age: Optional(30)))
▿ some: JSONDecoderDemo.SingerModel
▿ name: Optional("The Chainsmokers")
- some: "The Chainsmokers"
▿ age: Optional(30)
- some: 30
▿ name: Optional("Young")
- some: "Young"
▿ JSONDecoderDemo.SongModel
▿ singer: Optional(JSONDecoderDemo.SingerModel(name: Optional("The Chainsmokers"), age: Optional(30)))
▿ some: JSONDecoderDemo.SingerModel
▿ name: Optional("The Chainsmokers")
- some: "The Chainsmokers"
▿ age: Optional(30)
- some: 30
▿ name: Optional("All We Know")
- some: "All We Know"
这样这个嵌套就被解决了,接下来再挑战一个难度更大的,请看代码:
let album = """
{
"albumName": "Something Just Like This",
"releaseTime": "2017-02-22",
"songs": {
"favorite":[
{
"singer": {
"name":"The Chainsmokers",
"age": 30
},
"name": "Something Just Like This"
},
{
"singer": {
"name":"The Chainsmokers",
"age": 30
},
"name": "Closer"
},
{
"singer": {
"name":"The Chainsmokers",
"age": 30
},
"name": "Young"
Codable发布这么久我就不学,摸鱼爽歪歪,哎~就是玩儿