做过网络开发,特别是互联网,甚至移动端开发的,日常对于数据解析,早年主流的XML
,现今主流的JSON
都是非常熟悉的,说道解析,系统自带和各种第三方的解析库,除了解析当然也当不了懒癌的脚步,各种model反射库。 对于Objective-C
各种方案都尤为成熟,甚至还有专门的MacApp用于model生成,可以说是懒到极致,好处当然是节省出了撸猫撸手办的时间(技术狗撸手办不知道是什么时候开始的恶习,我还是更喜欢撸妹纸)。 这种操作对于Swift
就比较蛋疼了,当然第三方库和工具也是完全够用,但是,生成的model里面一大堆代码,一个字,恶心。
那好,今年Swift更新到4.0版本之后带了一个我最喜欢的功能:Codable 协议。
Codable
是Encodable 和Decodable 协议总和的别名。所以它既能编码也能解码,自从有了它,我model里面代码奏是干干净净,清清爽爽,对于洁癖控来说,这货是原生的,又可以少个Pod
和Package
了,巴巴掌。
开始吧 如果对此协议不太明白到底能干啥,可以先看下今年的WWDC 视频。
Codable
让我们可以通过Struct
和Class
不要一行多余代码来解析JSON
和Plist
数据。基础库简直嗨的不要不要的。 让我们来看一个例子:
1 2 3 4 5 6 import Foundation struct Swifter: Decodable { let fullName: String let id: Int let twitter: URL }
1 2 3 4 5 6 7 let json = """ { "fullName": "Federico Zanetello", "id": 123456, "twitter": "http://twitter.com/zntfdr" } """.data(using: .utf8)! // our data in native (JSON) format
1 2 let myStruct = try JSONDecoder().decode(Swifter.self, from: json) // Decoding our data print(myStruct) // decoded!!!!!
如果你看过Decodable文档 ,那你肯定知道里面有一个必须实现的方法init(from: Decoder)
。然而示例里并没实现,照样跑得飞起来,那就是苹果爸爸妈宝当到家:编译器默认会帮我们实现一个。
再看看另外一个例子:
1 2 3 4 5 6 7 8 9 10 11 12 import Foundation enum BeerStyle : String, Codable { case ipa case stout case kolsch // ... } struct Beer { let name: String let brewery: String let style: BeerStyle }
1 2 3 4 5 6 7 8 let json = """ { "name": "Endeavor", "abv": 8.9, "brewery": "Saint Arnold", "style": "ipa" } """.data(using: .utf8)! // our data in native (JSON) format
1 2 let myStruct = try JSONDecoder().decode(Swifter.self, from: json) // Decoding our data print(myStruct) // decoded!!!!!
看到这里是不是就被戳到G点了???如果后端没有啥特别的字段,你只需要把JSON
里的Key
作为Property
即可。 解析的条件就是,只要是系统提供的如String
,number
,Bool
以及各类集合,只要是符合Decodable
协议即可,简直是嗨到极点。
自定义实现 能够直接解析当然是最好的,但是往往开发的时候会遇到一些比较复杂的结构,那可能是Array
和Dictionary
相互嵌套,各个系统或者开发语言的保留字导致字段奇特,以及很多煞笔后端搞些魔术字啊,拼音命名的字段啥的。
实际上开发大多遇到的都是这种情况,那就不得不出动自定义解析了。自定义的部分稍微复杂点,坐稳了别翻车。
解码器 Decoder 负责处理JSON
和Plist
解析工作,需要重点关注的两个方法:
1 2 public func container<Key>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> where Key : CodingKey public func singleValueContainer() throws -> SingleValueDecodingContainer
这两个方法返回的都是Container
。 第一个方法返回keyed Container
,KeyedDecodingContainer :如果我们要自定义解析,那就需要告诉解码器如何映射。 第二个方法仅仅返回一个Container
,而SingleValueDecodingContainer 里的数据正是我们想要的。
容器 Decoder
提供了基础的功能解析原始数据,自定义数据就需要我们自己来搞定。KeyedDecodingContainer
:我们的容器是通过键值匹配的,所以大可以看作[Key: Any]
这样的字典结构。 不同的键对应不同的类型数据,所以容器提供的不同解码方法:decode(Type:forKey:)
。 它的神奇之处就在于容器会自动匹配数据类型。 当然,解码器也提供了通用方法:
1 public func decode<T>(_ type: T.Type, forKey key: KeyedDecodingContainer.Key) throws -> T where T : Decodable
有了这个泛型解析方法,那就意味着任意类型都可以匹配解析。
SingleValueDecodingContainer
就更前面说的一样,只考虑返回。
实现 前面基础已经铺垫完了,现在就不要编译器帮忙了,我们自己手动来实现init(from: Decoder)
。
第一步:选择正确的解码器 示例里是JSON
对象,那我们就选择JSONDecoder 。
1 let decoder = JSONDecoder()
JSON
和Plist
解析器都是系统内置的,如果你想要,你也可以自己实现一个解析器解析你够奇葩的数据。
第二步:选择正确的容器 JSON数据如下:
1 2 3 4 5 { "fullName" : "Federico Zanetello" , "id" : 123456 , "twitter" : "http://twitter.com/zntfdr" }
其实按照最开始说的,这个对象可以直接反射,辣么我们这里讲的自定义,那就按照自定义的套路走,我们按要求实现一个String
的结构体
,并且满足CodingKey
协议:
1 2 3 4 5 enum MyStructKeys: String, CodingKey { case fullName = "fullName" case id = "id" case twitter = "twitter" }
接下来创建容器:
1 let container = try decoder.container(keyedBy: MyStructKeys.self)
第三步:提取数据 这里我们需要做类型转换:
1 2 3 let fullName: String = try container.decode(String.self, forKey: .fullName) let id: Int = try container.decode(Int.self, forKey: .id) let twitter: URL = try container.decode(URL.self, forKey: .twitter)
第四步:初始化 使用默认的构造器:
1 let myStruct = Swifter(fullName: fullName, id: id, twitter: twitter)
现在我们就来看看全部实现:
1 2 3 4 5 6 7 8 9 10 11 12 import Foundation struct Swifter { let fullName: String let id: Int let twitter: URL init(fullName: String, id: Int, twitter: URL) { // default struct initializer self.fullName = fullName self.id = id self.twitter = twitter } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 extension Swifter: Decodable { enum MyStructKeys: String, CodingKey { // declaring our keys case fullName = "fullName" case id = "id" case twitter = "twitter" } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: MyStructKeys.self) // defining our (keyed) container let fullName: String = try container.decode(String.self, forKey: .fullName) // extracting the data let id: Int = try container.decode(Int.self, forKey: .id) // extracting the data let twitter: URL = try container.decode(URL.self, forKey: .twitter) // extracting the data self.init(fullName: fullName, id: id, twitter: twitter) // initializing our struct } }
1 2 3 4 5 6 7 let json = """ { "fullName": "Federico Zanetello", "id": 123456, "twitter": "http://twitter.com/zntfdr" } """.data(using: .utf8)! // our native (JSON) data
1 2 let myStruct = try JSONDecoder().decode(Swifter.self, from: json) // decoding our data print(myStruct) // decoded!
复杂结构处理 数组 1 2 3 4 5 6 import Foundation struct Swifter: Decodable { let fullName: String let id: Int let twitter: URL }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 let json = """ [ { "fullName": "Federico Zanetello", "id": 123456, "twitter": "http://twitter.com/zntfdr" }, { "fullName": "Federico Zanetello", "id": 123456, "twitter": "http://twitter.com/zntfdr" }, { "fullName": "Federico Zanetello", "id": 123456, "twitter": "http://twitter.com/zntfdr" } ] """.data(using: .utf8)! // our data in native format
1 2 let myStructArray = try JSONDecoder().decode([Swifter].self, from: json) myStructArray.forEach { print($0) } // decoded!!!!!
字典 1 2 3 4 5 6 import Foundation struct Swifter: Codable { let fullName: String let id: Int let twitter: URL }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 let json = """ { "one": { "fullName": "Federico Zanetello", "id": 123456, "twitter": "http://twitter.com/zntfdr" }, "two": { "fullName": "Federico Zanetello", "id": 123456, "twitter": "http://twitter.com/zntfdr" }, "three": { "fullName": "Federico Zanetello", "id": 123456, "twitter": "http://twitter.com/zntfdr" } } """.data(using: .utf8)! // our data in native format
1 2 let myStructDictionary = try JSONDecoder().decode([String: Swifter].self, from: json) myStructDictionary.forEach { print("\($0.key): \($0.value)") } // decoded!!!!!
枚举 1 2 3 4 5 6 7 8 9 10 import Foundation struct Swifter: Decodable { let fullName: String let id: Int let twitter: URL } enum SwifterOrBool: Decodable { case swifter(Swifter) case bool(Bool) }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 extension SwifterOrBool: Decodable { enum CodingKeys: String, CodingKey { case swifter, bool } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) if let swifter = try container.decodeIfPresent(Swifter.self, forKey: .swifter) { self = .swifter(swifter) } else { self = .bool(try container.decode(Bool.self, forKey: .bool)) } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 let json = """ [ { "swifter": { "fullName": "Federico Zanetello", "id": 123456, "twitter": "http://twitter.com/zntfdr" } }, { "bool": true }, { "bool": false }, { "swifter": { "fullName": "Federico Zanetello", "id": 123456, "twitter": "http://twitter.com/zntfdr" } } ] """.data(using: .utf8)! // our native (JSON) data
1 2 let myEnumArray = try JSONDecoder().decode([SwifterOrBool].self, from: json) // decoding our data myEnumArray.forEach { print($0) } // decoded!
嵌套结构 1 2 3 4 5 6 7 8 9 10 import Foundation struct Swifter: Decodable { let fullName: String let id: Int let twitter: URL } struct MoreComplexStruct: Decodable { let swifter: Swifter let lovesSwift: Bool }
1 2 3 4 5 6 7 8 9 10 let json = """ { "swifter": { "fullName": "Federico Zanetello", "id": 123456, "twitter": "http://twitter.com/zntfdr" }, "lovesSwift": true } """.data(using: .utf8)! // our data in native format
1 2 let myMoreComplexStruct = try JSONDecoder().decode(MoreComplexStruct.self, from: json) print(myMoreComplexStruct.swifter) // decoded!!!!!
结尾 为了避免必要的情况,也为了增强代码的健壮性,我们应该多使用系统的错误处理来避免经常崩溃:
1 2 3 4 5 do { let myStruct = try JSONDecoder().decode(Swifter.self, from: json) // do your decoding here } catch { print(error) // any decoding error will be printed here! }