半行代码实现字典转模型:Swift 4 Codable 协议

Swift 4最重大的一个变化就是增加了一个Codable协议,解决了在Swift中进行字典<->模型转换的问题。

在OC中,因为有runtime,我们能够比较方便的做字典转模型的操作,而在Swift中,没有了runtime,很难对字典转模型做自动操作,虽然网上现在也有一些第三方库,但要么使用繁琐,要么使用的是苹果不太推荐的做法。现在苹果推出了Codable协议,彻底解决了这个问题。

简介

Codable协议是EncodableDecodable协议的组合,看命名就可以知道,它们其中一个负责编码,一个负责解码。Swift中的一些基本类型:String,Int,Double,Date,Data,URL都已经遵循了这个协议。而我们自定义的类型想要遵循这个协议也非常简单,不需要写任何多余的代码:

1
2
3
4
5

struct Person: Codable {
let name: String
let age: Int?
}

只要一个类型中的属性都是 Codable 类型,那么这个类型就可以遵循 Codable 协议:

1
2
3
4
5

struct Book: Codable {
let price: Double
let author: Person
}

Swift自带的 ArrayDictionary 也可以遵循 Codable 协议,只要里面的元素都是 codable 类型:

1
2
3
4
5

struct Book: Codable {
let price: Double
let authors: [Person]
}

自动编码、解码

默认Coding Key

一个类型只要遵循了Codable协议,就能利用PropertyListEncoderJSONEncoder 进行自动解码和编码了,默认的Coding Key就是属性名:

1
2
3
4
5
6

let dic = ["name": "lijun", "age": 30] as [String : Any]
let data = try! JSONSerialization.data(withJSONObject: dic, options: [])
let person = try? JSONDecoder().decode(Person.self, from: data)

// person.name == "lijun", person.age == 30

自定义Coding Key

大部分情况,从API的字段命名规则和app端是不一样的,所以需要使用自定义的Coding Key。这也很方便,只需要在类型内部写一个 CodingKeys 枚举,并遵循CodingKes协议即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14

struct Person: Codable {
let name: String
let age: Int?

enum CodingKeys: String, CodingKey {
case name = "title"
case age
}
}

let dic = ["title": "lijun", "age": 30] as [String : Any]
let data = try! JSONSerialization.data(withJSONObject: dic, options: [])
let person = try? JSONDecoder().decode(Person.self, from: data)

手动编码、解码

有时候,API返回的数据结构并不一定能和模型的属性一一对上,比如可能只需要某个字段里的某一个字段,这时候就可以自己手动实现Decode协议:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

struct Person {
let name: String
let age: Int?

enum CodingKeys: String, CodingKey {
case name = "title"
case extraInfo = "extra_info"
}

enum ExtraInfoKeys: String, CodingKey {
case age
}
}

extension Person: Decodable {
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
name = try values.decode(String.self, forKey: .name)

let extraInfo = try values.nestedContainer(keyedBy: ExtraInfoKeys.self, forKey: .extraInfo)
age = try? extraInfo.decode(Int.self, forKey: .age)
}
}

let dic = ["title": "lijun", "extra_info": ["age": 30]] as [String : Any]
let data = try! JSONSerialization.data(withJSONObject: dic, options: [])
let penson = try? JSONDecoder().decode(Person.self, from: data)

手动实现Encodable协议同理:

1
2
3
4
5
6
7
8
9
extension Person: Encodable {
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(name, forKey: .name)

var extraInfo = container.nestedContainer(keyedBy: ExtraInfoKeys.self, forKey: .extraInfo)
try? extraInfo.encode(age, forKey: .age)
}
}

参考资料

坚持原创技术分享,您的支持将鼓励我继续创作!