Codable 是swift4的新特性, 使用其可以用更少的代码更快的实现数据编解码
JSON
一种基于文本的用于表示信息的格式,人类和计算机都很容易读写这种格式,也是目前使用最广泛的标准
简单写一个json文本
let json = """
{
"name" : "xiaoming",
"age" : 18,
"score" : 99.5
}
""".data(using: .utf8)!
复制代码
我们用上面的json来模拟网络数据请求得到的结果,下面构建model和解析
构建模型
我们创建一种适合这种json表示的swift类型 也就是model, 让json中的值和swift系统中定义类型互相匹配。
- JSON中的对象是无序键值对,和swift中包含
String
的Dictionary
类似 - JSON 将数字表示成一系列数,不受任何语义的影响,它不区分整数和浮点数,不区分定长和非定长数字,也不区分是二进制还是十进制。每个实现自行决定如何解释这些数字。
- 对于具有 Optional 属性的类型, Codable 可以自动将 null 映射为 nil。
struct Student : Codable {
var name : String
var age : Int
var score : Double
}
复制代码
我们首先定义一个结构体 Student
来对应数据中的顶层对象, 这里我们使用结构体,使用 Class
也无妨。
Codable 协议简介
上面的结构体 Student
遵循了 Codable
协议, 其大大提升了对象和其表示之间相互转换的体验。
理解 Codable 最好的方式就是看它的定义:
typealias Codable = Decodable & Encodable
Codable
是一种 混合类型,由 Decodable
和 Encodable
协议构成。
Decodable
协议定义了一个初始化函数:
init(from decoder: Decoder) throws
遵从 Decodable
协议的类型可以使用任何 Decoder 对象进行初始化。
Encodable
协议定义了一个方法:
func encode(to encoder: Encoder) throws
任何 Encoder
对象都可以创建遵从了 Encodable 协议类型的表示。
只有当一个类型满足这个协议的所有要求的时候,我们才能说这个类型 遵从 那个协议了。 对于 Decodable
而言,唯一的要求就是 init(from:)
初始化方法。
init(from:)
初始化方法接受一个 Decoder
参数。 Decoder 协议需要阐明将 Decodable 对象的表示解码成对象的要求。为了适应各种数据交换格式,解码器和编码器都使用名为 容器(container)的抽象。容器是用来存储值的,可以存储一个值也可以存储多个值,可以像字典一样有键去对应值,也可以像数组一样不需要键。...
因为上面那个 JSON 表示的顶层有一个对象, 所以我们就创建一个有键的容器,然后用不同的键来解码各个属性。
我们创建一个 CodingKeys
枚举,定义属性名称和容器的键之间的映射。这个枚举声明其原始类型是 String
,同时声明使用 CodingKey 协议。因为每个名字都和 JSON 中的键相同,所以我们不用为这个枚举提供明确的原始值。
struct Student: Decodable {
// ...
private enum CodingKeys: String, CodingKey {
case name
case age
case score
}
}...
复制代码
接下来,在 init(from:)
初始化函数里我们创建一个键控容器,调用decoder
的 container(keyedBy:)
方法,并传入 CodingKeys
作为参数。
最后,我们调用 container
的 decode(_:,forKey:)
方法把每个属性初始化一下。
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.score = try container.decode(Double.self, forKey: .score)
}...
复制代码
我们有了一个 Student
模型,也遵从了 Decodable
协议,现在我们可以从 JSON 表示中创建一个 student
对象了
把 JSON 解码成模型对象
先引用 Foundation
,以便使用 JSONDecoder
和 JSONEncoder
。
import Foundation
创建一个 JSONDecoder
对象并调用其 decode(_:from:)
方法
let decoder = JSONDecoder()
let student = try! decoder.decode(Student.self, from: json)
复制代码
试验转换成功与否
print(student.name)
print(student.age)
print(student.score)
复制代码
将模型对象编码为 JSON
下面我们实现 Encodable
协议要求的 encode(to:)
方法。 encode(to:)
和 init(from:)
就像一对镜像一样工作。 先调用 encoder
的 container(keyedBy:)
方法创建一个 container
, 和上一步一样,传入一个 CodingKeys.self
参数。 这里我们把 container
当作一个变量(使用 var
而不是 let
), 因为这个方法做的是填充 encoder
的参数,需要进行修改。我们对每个属性调用一次 encode(_:forKey:)
并传入属性的值和它对应的键。...
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.name, forKey: .name)
try container.encode(self.age, forKey: .age)
try container.encode(self.score, forKey: .score)
}...
复制代码
let encoder = JSONEncoder()
let reencodedJSON = try! encoder.encode(stuent)
print(String(data: reencodedJSON, encoding: .utf8)!)...
复制代码
JSON 对空格和空行(whitespace)不敏感,但是你可能会在意。如果你觉得输出不美观,把 JSONEncoder
的 outputFormatting
属性设置为 .prettyPrinted
就好了
删除无用的代码
把所有我们写的 Codable
的实现都删掉。这样,应该就只剩下结构和属性的定义了。
struct Student : Codable {
var name : String
var age : Int
var score : Double
}
复制代码
直接运行代码,试试编码和解码 JSON。有什么变化吗?完全没有。 这让我们看到了 Codable 的杀手级特性:
Swift 自动整合了 Decodable
和 Encodable
的一致性。
一种类型只要在其声明中采用协议即可,也就是说,不用在扩展中再做额外的事,只要它的每个属性都有一个符合其协议,其他一切都自动完成了。
尝试解析各种格式的JSON文本
JSON顶层是数组的情况:
let jsonModels = """
[
{
"name" : "xiaoming",
"age" : 18,
"score" : 99.5
},
{
"name" : "daxiong",
"age" : 19,
"score" : 66
}
]
""".data(using: .utf8)!
复制代码
把它解析成 Student 对象的数组十分简单:像之前一样调用 decode(_:from:)
,把 Student.self
换成 [Student].self
以便去解析 Student
数组而不是单个对象。
let students = try! decoder.decode([Student].self, from: jsonModels)
为什么这么简单就可以了?这要归功于 Swift 4 的另一个新特性:条件一致性。
// swift/stdlib/public/core/Codable.swift.gyb
extension Array : Decodable where Element : Decodable {
// ...
}
复制代码
我们再在数组的外层套一个字典的形式:
let jsonComplex = """
{
"students" : [
{
"name" : "xiaoming",
"age" : 18,
"score" : 99.5
},
{
"name" : "daxiong",
"age" : 19,
"score" : 66
}
]
}
""".data(using: .utf8)!
复制代码
如果 Array
和Dictionary
包含的 KeyType
和 ValueType
都遵从 Decodable
协议,那么这样的数组或字典本身也就遵从了 Decodable
协议:
// swift/stdlib/public/core/Codable.swift.gyb
extension Dictionary : Decodable where Key : Decodable,
Value : Decodable {
// ...
}...
复制代码
因此你可以把之前 decode(_:from:)
的调用参数直接换成 [String: [Student]].self
(Dictionary<String, Plane>
的简写):
let jsonData = try! decoder.decode([String: [Student]].self, from: jsonComplex)
let students = jsonData["students"]
复制代码
或者你也可以创建一个遵从 Decodable
协议的新类型,然后给这个类型一个属性来放 [Student] ,该属性的名字应和 JSON 中的键名相对应,再把这个新类型传入 decode(_:from:)
函数中:
struct Brige: Decodable {
var students: [Student]
}
let Brige = try! decoder.decode(Brige.self, from: json)
let students = Brige.students...
复制代码
Codable
是一种 混合类型,由Decodable
和Encodable
协议构成。- 只要一种类型中的每个属性都遵从协议,在类型定义时声明一下,Swift 就会自动整合
Decodable
和Encodable
。 - 如果
Array
或Dictionary
中的每一个元素都遵从Codable
类型,那么它们本身也会遵从Codable