自己动手 打造Swift全功能 JSON 解析、生成库

移动开发
在我动手搞这个 JSON 解析库之前,我一直在用 SwiftyJSON 这个库,这个库是国人开源的最受欢迎的 Swift 项目,没有之一,也是全球最受欢迎的 Swift 库第二名,第一名是网络库 Alamofire。由于要实现 ["key"]["key1"] 这样的递归查找,我一直觉得 JSON 解析库非常复杂难搞。

准备工作

起因

在我动手搞这个 JSON 解析库之前,我一直在用 SwiftyJSON 这个库,这个库是国人开源的***的 Swift 项目,没有之一,也是全球***的 Swift 库第二名,***名是网络库 Alamofire。由于要实现 ["key"]["key1"] 这样的递归查找,我一直觉得 JSON 解析库非常复杂难搞。

过程

最近比较闲,我打算把之前用过的开源库都自己实现一下,提升一下自己。而且我在实际使用 SwiftyJSON 的过程中,遇到过非合法长字符串导致奔溃的情况,我打算先从 JSON 解析库下手,于是中秋节的前一天,吃完午饭我就开搞了,到了下午六七点,解析的功能就全部搞定了,十分出乎预料。中秋节这天我又把生成的功能做了,整理下代码,收拾收拾就给开源了。

API 统计

言归正传,我们的准备工作还是要做的:统计 SwiftyJSON 的主要 API。经过简单统计,我找到了所有我在项目中使用过的 SwiftyJSON 的 API,主要分为四类:

通过特定路径取出特定类型的值,如:json["key"]["key1"].stringValue

取出某个数组类型的子 JSON,循环拿到里面的值

将某个 JSON 对象格式化成字符串

使用 Dictionary 生成 JSON 对象

递归取值

设计基本结构

既然要兼容 SwiftyJSON 的主要 API,那调用方式跟它一样就行了:先使用 NSData、Array 或者 Dictionary 生成 JSON 对象,再对这个对象进行操作,拿到我们想要的值、数组、完整的 JSON 字符串等。

为了对比 API 的执行结果,我们仍然引入 SwiftyJSON 库,所以我们需要一个其他的类名,在这里我们就暂定为 JSONND,是 JSON Never Die 的缩写,含义是永不奔溃的 JSON 解析库。

我们先从网络数据下手。网络数据的来源一般为 NSData,经过简单查询我们知道系统提供了一个 JSON 解析方法,可以把 NSData 格式的解析为 AnyObject,构造出 JSONND 类:

  1. public struct JSONND { 
  2. public var jsonObject: AnyObject! 
  3. public static func initWithData(data: NSData) -> JSONND! { 
  4. do { 
  5. return JSONND(jsonObject: try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.AllowFragments)) 
  6. catch let error as NSError { 
  7. let e = NSError(domain: "JSONNeverDie.JSONParseError", code: error.code, userInfo: error.userInfo) 
  8. NSLog(e.localizedDescription) 
  9. return JSONND() 

需要注意的是,我们给 JSON 类使用的是 struct 结构体,为了它能够具备自动初始化函数,值类型等优良特性。JSON 直观上感觉是 String 的衍生,故使用值类型也起到降低学习成本的作用。

我们使用下面的代码来检验成果:

  1. let data = NSData(contentsOfURL: NSURL(string: "http://httpbin.org/get?hello=world")!)! 
  2. let json = JSONND.initWithData(data) 

运行,正常,初始化代码完成。

支持 ["key"]["key1"] 形式的递归取值

为了支持递归取值,同时不让我们的 JSONND 结构体变的过于臃肿,我们考虑将递归取值的任务交给第二个结构体:

  1. public struct JSONNDElement { 
  2. public var data: AnyObject! 
  3. public init(data: AnyObject!) { 
  4. self.data = data 
  5. public subscript (index: String) -> JSONNDElement { 
  6. if let jsonDictionary = self.data as? Dictionary { 
  7. if let value = jsonDictionary[index] { 
  8. return JSONNDElement(data: value) 
  9. else { 
  10. NSLog("JSONNeverDie: No such key '\(index)'"
  11. return JSONNDElement(data: nil) 

同时,我们需要在 JSONND 结构体中触发递归取值的***次:

  1. public subscript (index: String) -> JSONNDElement { 
  2. let jsonNDElement = JSONNDElement(data: self.jsonObject) 
  3. return jsonNDElement[index] 

检验成果:

  1. let data = NSData(contentsOfURL: NSURL(string: "http://httpbin.org/get?hello=world")!)! 
  2. let json = JSONND.initWithData(data) 
  3. let args = json["args"
  4. let hello = args["hello"

运行,正常,递归取值完成。

取出 Int、Float、String、Array、Bool 类型的值

在我们通过 ["key"]["key1"] 的形式拿到最终的 JSONNDElement 对象之后,我们就需要把他的 data 转换成我们想要的类型输出了。介绍 JSON 数据类型的文档:http://www.yiibai.com/json/json_data_types.html

SwiftyJSON 采用两级函数来取值,即 .int 为 Int? 类型, .intValue 为 Int 类型,这显然是为了适应不同的 API 设计作出的兼容,我们也要实现这样的两级取值。要实现取值,其实是非常简单的,if let 转换一下类型,基本就 OK 了:

  1. public var int: Int? { 
  2. get { 
  3. if let _ = self.data { 
  4. return self.data.integerValue 
  5. else { 
  6. return nil 
  7. public var intValue: Int { 
  8. get { 
  9. if let i = self.int { 
  10. return i 
  11. else { 
  12. return 0 

由于代码比较繁琐无趣,这里只用 Int 展示一下,更多代码请见 Github。

Array 类型的处理要单独拿出来处理,因为 Array 有子级,所以我们得到的将是 JSONNDElement 数组。Array 处理代码如下:

  1. public var array: [JSONNDElement]? { 
  2. get { 
  3. if let _ = self.data { 
  4. if let arr = self.data as? Array { 
  5. var result = Array() 
  6. for i in arr { 
  7. result.append(JSONNDElement(data: i)) 
  8. return result 
  9. else { 
  10. return nil 
  11. else { 
  12. return nil 
  13. public var arrayValue: [JSONNDElement] { 
  14. get { 
  15. if let i = self.array { 
  16. return i 
  17. else { 
  18. return [] 

将 JSONND 对象格式化成字符串

通过在 JSONND 和 JSONNDElement 中添加两个函数,将成员变量 data 转换成 String 就可以加上这个功能了:

JSONND 中:

  1. public var jsonString: String? { 
  2. let jsonNDElement = JSONNDElement(data: self.jsonObject) 
  3. return jsonNDElement.jsonString 
  4. public var jsonStringValue: String { 
  5. let jsonNDElement = JSONNDElement(data: self.jsonObject) 
  6. return jsonNDElement.jsonStringValue 

JSONNDElement 中:

  1. public var jsonString: String? { 
  2. get { 
  3. do { 
  4. if let _ = self.data { 
  5. return NSString(data: try NSJSONSerialization.dataWithJSONObject(self.data, options: .PrettyPrinted), encoding: NSUTF8StringEncoding) as? String 
  6. else { 
  7. return nil 
  8. catch { 
  9. return nil 
  10. public var jsonStringValue: String { 
  11. get { 
  12. if let i = self.jsonString { 
  13. return i 
  14. else { 
  15. return "" 

使用 Array、Dictionary 生成 JSON 对象

这一步操作我们将使用从 SwiftyJSON 中偷来的函数,稍加改装就可以利用了:

  1. // stolen from SwiftyJSON 
  2. extension JSONND: DictionaryLiteralConvertible { 
  3. public init(dictionaryLiteral elements: (String, AnyObject)...) { 
  4. self.init(jsonObject: elements.reduce([String : AnyObject]()){(dictionary: [String : AnyObject], element:(String, AnyObject)) -> [String : AnyObject] in 
  5. var d = dictionary 
  6. d[element.0] = element.1 
  7. return d 
  8. }) 
  9. // stolen from SwiftyJSON 
  10. extension JSONND: ArrayLiteralConvertible { 
  11. public init(arrayLiteral elements: AnyObject...) { 
  12. self.init(jsonObject: elements) 

代码的原理也很简单,利用系统的自动转换 protocol:DictionaryLiteralConvertible 和 ArrayLiteralConvertible,让 Array 和 Dictionary 自动转换为 JSONND 类型。现在我们可以采用这种方式定义 JSONND 对象了:

  1. let dictionaryJSON: JSONND = ["a"1"b": [123]] 
  2. let arrayJSON: JSONND = [012

搞定!

检验成果

我已经给 JSONNeverDie 项目写了完整的单元测试来测试每一项功能,感兴趣的同学可以去 Github 查看测试代码。

责任编辑:chenqingxiang 来源: CocoaChina
相关推荐

2023-12-15 10:14:42

数据库select语句

2015-09-01 09:49:28

2009-03-16 16:30:18

2013-11-29 14:07:29

编程产品

2013-11-26 13:11:20

编程优秀产品移动应用

2009-12-10 18:24:17

2009-01-08 09:52:26

2009-02-20 12:09:10

数据备份数据安全NAS

2022-05-11 09:50:02

GitLinux

2017-02-14 10:20:43

Java Class解析器

2011-08-25 09:30:22

2016-11-29 10:20:50

SwiftJSONModel工具库

2016-04-11 09:58:53

iOSJSONModel

2009-12-07 10:27:51

WCF分页

2020-09-29 12:13:46

SQL引擎底层

2010-09-02 15:28:30

DHCP功能

2020-05-20 13:53:41

HTTP环境安装

2021-02-21 13:40:12

Windows 10Windows微软

2015-06-02 10:24:43

iOS网络请求降低耦合

2015-01-20 09:11:19

点赞
收藏

51CTO技术栈公众号