自己动手使用 Swift 打造全功能 JSON 解析、生成库
开源项目:JSONNeverDie,纯 Swift 开发的全功能 JSON 解析、生成库,兼容 SwiftyJSON 主要 API:https://github.com/johnlui/JSONNeverDie
本篇文章中,我将跟大家一起,一步一步构造出一个好用的 JSON 解析和生成的库。
准备工作
起因
在我动手搞这个 JSON 解析库之前,我一直在用 SwiftJSON 这个库,这个库是国人开源的最受欢迎的 Swift 项目,没有之一,也是全球最受欢迎的 Swift 库第二名,第一名是网络库 Alamofire。由于要实现 ["key"]["key1"] 这样的递归查找,我一直觉得 JSON 解析库非常复杂难搞。
过程
最近比较闲,我打算把之前用过的开源库都自己实现一下,提升一下自己。而且我在实际使用 SwiftyJSON 的过程中,遇到过非合法长字符串导致奔溃的情况,我打算先从 JSON 解析库下手,于是中秋节的前一天,吃完午饭我就开搞了,到了午夜左右,解析的功能就全部搞定了,十分出乎预料。中秋节这天我又把生成的功能做了,整理下代码,收拾收拾就给开源了。
API 统计
言归正传,我们的准备工作还是要做的:统计 SwiftJSON 的主要 API。经过简单统计,我找到了所有我在项目中使用过的 SwiftJSON 的 API,主要分为四类:
通过特定路径取出特定类型的值,如:json["key"]["key1"].stringValue
取出某个数组类型的子 JSON,循环拿到里面的值
将某个 JSON 对象格式化成字符串
使用 Dictionary 生成 JSON 对象
递归取值
设计基本结构
既然要兼容 SwiftJSON 的主要 API,那调用方式跟它一样就行了:先使用 NSData、Array 或者 Dictionary 生成 JSON 对象,再对这个对象进行操作,拿到我们想要的值、数组、完整的 JSON 字符串等。
为了对比 API 的执行结果,我们仍然引入 SwiftJSON 库,所以我们需要一个其他的类名,在这里我们就暂定为 JSONND,是 JSON Never Die 的缩写,含义是永不奔溃的 JSON 解析库。
我们先从网络数据下手。网络数据的来源一般为 NSData,经过简单查询我们知道系统提供了一个 JSON 解析方法,可以把 NSData 格式的解析为 AnyObject,构造出 JSONND 类:
public struct JSONND {
public var jsonObject: AnyObject!
public static func initWithData(data: NSData) -> JSONND! {
do {
return JSONND(jsonObject: try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.AllowFragments))
} catch let error as NSError {
let e = NSError(domain: "JSONNeverDie.JSONParseError", code: error.code, userInfo: error.userInfo)
NSLog(e.localizedDescription)
return JSONND()
}
}
}需要注意的是,我们给 JSON 类使用的是 struct 结构体,为了它能够具备自动初始化函数,值类型等优良特性。JSON 直观上感觉是 String 的衍生,故使用值类型也起到降低学习成本的作用。
我们使用下面的代码来检验成果:
let data = NSData(contentsOfURL: NSURL(string: "http://httpbin.org/get?hello=world")!)! let json = JSONND.initWithData(data)
运行,正常,初始化代码完成。
支持 ["key"]["key1"] 形式的递归取值
为了支持递归取值,同时不让我们的 JSONND 结构体变的过于臃肿,我们考虑将递归取值的任务交给第二个结构体:
public struct JSONNDElement {
public var data: AnyObject!
public init(data: AnyObject!) {
self.data = data
}
public subscript (index: String) -> JSONNDElement {
if let jsonDictionary = self.data as? Dictionary<String, AnyObject> {
if let value = jsonDictionary[index] {
return JSONNDElement(data: value)
} else {
NSLog("JSONNeverDie: No such key '\(index)'")
}
}
return JSONNDElement(data: nil)
}
}同时,我们需要在 JSONND 结构体中触发递归取值的第一次:
public subscript (index: String) -> JSONNDElement {
let jsonNDElement = JSONNDElement(data: self.jsonObject)
return jsonNDElement[index]
}检验成果:
let data = NSData(contentsOfURL: NSURL(string: "http://httpbin.org/get?hello=world")!)! let json = JSONND.initWithData(data) let args = json["args"] 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 了:
public var int: Int? {
get {
if let _ = self.data {
return self.data.integerValue
} else {
return nil
}
}
}
public var intValue: Int {
get {
if let i = self.int {
return i
} else {
return 0
}
}
}由于代码比较繁琐无趣,这里只用 Int 展示一下,更多代码请见 Github。
Array 类型的处理要单独拿出来处理,因为 Array 有子级,所以我们得到的将是 JSONNDElement 数组。Array 处理代码如下:
public var array: [JSONNDElement]? {
get {
if let _ = self.data {
if let arr = self.data as? Array<AnyObject> {
var result = Array<JSONNDElement>()
for i in arr {
result.append(JSONNDElement(data: i))
}
return result
} else {
return nil
}
} else {
return nil
}
}
}
public var arrayValue: [JSONNDElement] {
get {
if let i = self.array {
return i
} else {
return []
}
}
}将 JSONND 对象格式化成字符串
通过在 JSONND 和 JSONNDElement 中添加两个函数,将成员变量 data 转换成 String 就可以加上这个功能了:
JSONND 中:
public var jsonString: String? {
let jsonNDElement = JSONNDElement(data: self.jsonObject)
return jsonNDElement.jsonString
}
public var jsonStringValue: String {
let jsonNDElement = JSONNDElement(data: self.jsonObject)
return jsonNDElement.jsonStringValue
}JSONNDElement 中:
public var jsonString: String? {
get {
do {
if let _ = self.data {
return NSString(data: try NSJSONSerialization.dataWithJSONObject(self.data, options: .PrettyPrinted), encoding: NSUTF8StringEncoding) as? String
} else {
return nil
}
} catch {
return nil
}
}
}
public var jsonStringValue: String {
get {
if let i = self.jsonString {
return i
} else {
return ""
}
}
}使用 Array、Dictionary 生成 JSON 对象
这一步操作我们将使用从 SwiftyJSON 中偷来的函数,稍加改装就可以利用了:
// stolen from SwiftyJSON
extension JSONND: DictionaryLiteralConvertible {
public init(dictionaryLiteral elements: (String, AnyObject)...) {
self.init(jsonObject: elements.reduce([String : AnyObject]()){(dictionary: [String : AnyObject], element:(String, AnyObject)) -> [String : AnyObject] in
var d = dictionary
d[element.0] = element.1
return d
})
}
}
// stolen from SwiftyJSON
extension JSONND: ArrayLiteralConvertible {
public init(arrayLiteral elements: AnyObject...) {
self.init(jsonObject: elements)
}
}代码的原理也很简单,利用系统的自动转换 protocol:DictionaryLiteralConvertible 和 ArrayLiteralConvertible,让 Array 和 Dictionary 自动转换为 JSONND 类型。现在我们可以采用这种方式定义 JSONND 对象了:
let dictionaryJSON: JSONND = ["a": 1, "b": [1, 2, 3]] let arrayJSON: JSONND = [0, 1, 2]
搞定!
检验成果
我已经给 JSONNeverDie 项目写了完整的单元测试来测试每一项功能,感兴趣的同学可以去 Github 查看测试代码。
建议继续学习:
- JSON和JSONP的区别 (阅读:8094)
- XML和JSON (阅读:7328)
- 如何编写一个JSON解析器 (阅读:5948)
- 理解JSON:3分钟课程 (阅读:5339)
- 用 JavaScript 对 JSON 进行模式匹配 (Part 1 - 设计) (阅读:4976)
- 前端模板引擎 (阅读:4240)
- 用 JavaScript 对 JSON 进行模式匹配 (Part 2 - 实现) (阅读:4291)
- Ajax和WEB服务数据格式:JSON JSONP (阅读:4119)
- OpenStack Swift源码导读之——业务整体架构和Proxy进程 (阅读:3968)
- [python]定制JSON中的浮点数格式 (阅读:3521)
扫一扫订阅我的微信号:IT技术博客大学习
- 作者:JohnLui 来源: 岁寒
- 标签: JSON Swift
- 发布时间:2015-11-08 22:12:13
-
[939] WordPress插件开发 -- 在插件使用 -
[117] 解决 nginx 反向代理网页首尾出现神秘字 -
[50] 如何保证一个程序在单台服务器上只有唯一实例( -
[48] 整理了一份招PHP高级工程师的面试题 -
[48] 用 Jquery 模拟 select -
[48] 海量小文件存储 -
[47] ps 命令常见用法 -
[47] Innodb分表太多或者表分区太多,会导致内 -
[46] 全站换域名时利用nginx和javascri -
[45] find命令的一点注意事项
