Swift JSON 教程:使用 JSON
原文:Swift JSON Tutorial: Working with JSON
作者:Luke Parham
译者:kmyhy2017-1-15 更新说明:本教程由 Luke Parham 更新为 Xcode 8.2 和 Swift 3。原文作者是 Attila Hegedüs。
JavaScript Object Notation,简称 JSON,是一种常用的和 web 服务进行数据传输的方式。它易于使用和阅读,因此使用者众多。
以下列 JSON 为例:
[{"person": {"name": "Dani","age": "24"}},{"person": {"name": "Ray","age": "70"}}
]
在 O-C 中,解析这段 JSON 非常简单:
NSArray *json = [NSJSONSerialization JSONObjectWithData:JSONData options:kNilOptions error:nil];
NSString *age = json[0][@"person"][@"age"];
NSLog(@"Dani's age is %@", age);
在 Swift 中,要解析这段 JSON 就相当麻烦了,因为 Swift 有 optional 和类型安全的限制:
var json: [Any]?
do {json = try JSONSerialization.jsonObject(with: data)
} catch {print(error)
}
guard let item = json?.first as? [String: Any],let person = item["person"] as? [String: Any],let age = person["age"] as? Int else {return
}
由于 guard 语句的存在,我们摆脱了厄运金字塔代码,但仍然需要些大量重复的代码才能访问 JSON 字符串中的数据。
在这篇 Swift JSON 教程里,我们一开始会用 Swift 的原生方法来解析 JSON——不使用任何第三方库。这实际上是苹果推荐的做法,因为 Swift 内置的工具在大部分场景下都够我们用了。
但是,就像每个人都会偶尔发出叛逆的声音,你也可以阅读本教程的下半部分,学习如何用 Gloss 框架节省你的时间。
注意:Gloss 只是众多 JSON 框架之一,但它却是一个好的设计模式的例子,我们会很好地证明这一点。
我们会用这两种方式对一个 JSON 文件进行解析,这个文件列出了美国 App 商店中上榜“25 个最流行的 app”的应用。
开始
因为学习 JSON 并不需要用户界面,因此我们所有的练习都是在 playground 中进行的。请在这里下载开始项目。
打开 Swift.playground 。
注意:你可能发现 playground 的项目导航器是关闭的。如果是这样,请用 command+1 打开它。
开始项目中有几个源文件和资源文件,这是为了将你的注意力集中在用 Swift 解析 JSON 的主题上来。看一下项目结构,以了解大概的内容:
Resources 文件夹中包含了你要解析的示例 JSON 文件。
- topapps.json: 包含了 JSON 字符串。
Sources 文件夹中包含在主 playground 代码中能够调用的其它 Swift 源文件。将这些源文件放在 Sources 文件夹中是为了让你的 playground 干净、可读行更高。
- App.swift:一个简单的 Swift 结构,代表了 app。
- DataManager.swift: 负责网络或本地数据的抓取。在本教程中我们会用这个文件中的方法来加载 JSON 数据。
你可以浏览一下 playground 中内容,再继续后面的内容!
原生 Swift JSON 解析
看完示例的 JSON 文件之后,我们可以来解析它并打印出排名第一的 app 是什么!
使用 optional 的初始化方法
首先,打开 App.swift。里面有一个简单的模型对象,定义了一个初始化方法,使用一个名字和一个链接作为初始化参数。
这个 JSON 文件的 entry 下面有一个对象数组,每个对象都代表了一个 App 对象。
根据这个结构,我们要在原来的初始化方法下面新增一个 optional 的初始化方法。
//1
public init?(json: [String: Any]) {//2guard let container = json["im:name"] as? [String: Any],let name = container["label"] as? String,let id = json["id"] as? [String: Any],let link = id["label"] as? String else {return nil}//3self.name = nameself.link = link
}
这个初始化方法使用一个 [String:Any] 字典作为参数,这个字典用 JSON 数据来构造。这是可以的,有效的 JSON 要么是数组要么是单个值,但是我们已经确定它就是一个字典。
然后,用 guard-let 语法去解析 JSON,取出其中的 name 和 link 值。
最后,如果所有值都不为空,则填充模型对象的属性并返回模型。
创建好模型之后,在项目导航器中点击 TopApps-Starter,打开主 playground 文件。
首先,在 getTopAppsDataFromFileWithSuccess 方法的 success 块中添加 guard 语句:
guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {PlaygroundPage.current.finishExecution()
}
这里,我们用 jsonObject(with:options:) 方法将 JSON 字符串转换为 JSON 对象。
然后,我们进行第一步解析,获得代表第一个 App 的 JSON 对象。
guard let feed = json?["feed"] as? [String: Any],let apps = feed["entry"] as? [[String: Any]],let firstApp = apps.first else {PlaygroundPage.current.finishExecution()
}
这一步,我们首先取得最外层的 feed 对象,它的 entry 键中包含了 app 数组。
最后,调用我们早先写好的 optional 的初始化函数获得排名第一的 app:
let app = App(json: firstApp)
print(app ?? "Failed to initialize")
运行 app,控制台输出如下:
App(name: "Game of War - Fire Age", link: "https://itunes.apple.com/us/app/game-of-war-fire-age/id667728512?mt=8&uo=2")
好了—— “Game of War – Fire Age” 就是在这个 JSON 文件中排名第一的 app。
使用能够抛出异常的初始化方法
在初始化失败时返回一个 nil 而不是实例对象,并不能告诉我们到底发生了什么错误。
打开 App.swift ,定义一个错误枚举。
enum SerializationError: Error {case missing(String)
}
然后,将 optional 初始化方法换成下面的初始化方法。当 JSON 参数中包含有空值时,这个初始化方法会抛出一个上面定义的错误。
public init(json: [String: Any]) throws {//1guard let container = json["im:name"] as? [String: Any],let name = container["label"] as? String else {throw SerializationError.missing("name")}guard let id = json["id"] as? [String: Any],let link = id["label"] as? String else {throw SerializationError.missing("link")}//2self.name = nameself.link = link
}
这里,我们为每个属性使用一个单独的 guard 语句。当传入的 JSON 参数中包含无效的值时,抛出一个该属性未找到的错误。
最后,如果没有任何异常发生,我们填充属性,返回有效的实例对象。
打开主 playground 将这 2 句:
let app = App(json: firstApp)
print(app ?? "Failed to initialize")
替换为:
do {let app = try App(json: firstApp)print(app)
} catch let error {print(error)
}
这里需要使用 do-catch 块。如果初始化成功,我们不需要担心 app 会出现控制。如果初始化失败,我们会得到一个有用的错误信息,而不是一个空对象。
要查看初始化失败的效果,请将 App(json: firstApp) 换成 App(json: [:])。
用 Gloss 解析 JSON
你已经体验了如果将 JSON 解析成自己的模型对象,接下来我们来尝试另一种解析方法。
为了保持美观、大方,我们创建一个新的 playground 叫做 Gloss.playground。然后,将 topapps.json 拷贝到 Resources 目录,将 DataManager.swift 拷贝到 Source。
将 Gloss 集成到项目中
将 Gloss 集成到项目或 playground 中很简单:
- 从这里下载 Gloss 源代码 zip 包。
- 解压缩,然后将 Gloss-master/Sources 文件夹拖到 playround 的 Source 目录。
项目导航器看起来是这个样子:
https://koenig-media.raywenderlich.com/uploads/2017/01/Screen-Shot-2017-01-16-at-12.56.00-AM-411x500.png’ width=’300’/>
这就好了!下载可以在我们的 playground 中使用 Gloss 了,这是一种“简单”的 JSON 解析方法!
将 JSON 映射为对象
首先,必须定义模型对象和 JSON 对象的映射方式。
模型对象必须实现 Decodeable 协议,这个协议允许它们从 JSON 进行解码。要实现这个协议,需要实现 init?(json: JSON) 初始化方法。
注意:打开 Gloss.swift。在 Decodable 协议中,如果用 ⌘+左键点击 JSON 查看它的定义,你会看到它仅仅是在同一个文件中被定义为 [String: Any] 的别名。
TopApps
TopApps 模型用于表示顶级对象,它只包含一个键值对:
{"feed": {...}
}
创建一个新的 Swift 文件,名为 TopApps.swift,保存到 playground 的 Sources 文件夹下。编辑它的代码为:
public struct TopApps: Decodable {// 1public let feed: Feed?// 2public init?(json: JSON) {feed = "feed" <~~ json}}
首先要定义模型所用到的属性。这里我们只有一个属性。先别管 Feed 报出的异常,我们会在后面定义 Feed 模型类。
为了实现 Decodable 协议,TopApps 必须实现 optional 的初始化方法。
可能 <~~ 运算符有点陌生。它是 Encode 运算符,在 Gloss 的 Operators.swift 文件中定义。因为 Feed 也是一个 Decodable 类型,Gloss 可以把编码工作交给这个对象。
Feed
Feed 对象和顶级对象很像。它有两个键值对,但由于我们只对上榜的 25 个 app 感兴趣,因此 author 对象其实是没有必要处理的。
{"author": {...},"entry": [...]
}
新建一个 Swift 文件,名为 Feed.swift,保存到 Sources 文件夹下:
public struct Feed: Decodable {public let entries: [App]?public init?(json: JSON) {entries = "entry" <~~ json}}
App
App 是最后一个模型对象,它表示一个 app 对象:
{"im:name": {"label": "Game of War - Fire Age"},"id": {"label": "https://itunes.apple.com/us/app/game-of-war-fire-age/id667728512?mt=8&uo=2", ...},...
}
新建 App.swift 文件,保存到 Sources 文件夹下,编辑它的代码为:
public struct App: Decodable {// 1public let name: Stringpublic let link: Stringpublic init?(json: JSON) {// 2guard let container: JSON = "im:name" <~~ json,let id: JSON = "id" <~~ json else {return nil}guard let name: String = "label" <~~ container,let link: String = "label" <~~ id else {return nil}self.name = nameself.link = link}
}
Feed 和 TopApps 都使用 optional 属性。但当我们确定 JSON 中某个值肯定存在时,可以用非 optional 的属性。
我们并不需要为 JSON 的每个成员都创建一个模型对象。例如,这里就没有为 in:name 和 id 创建模型对象。当我们使用非可空对象和嵌套对象时,一定要进行非空校验。
现在模型类已经准备好了,我们该让 Gloss 干活了!
打开 playground 文件,将它的内容替换为:
import UIKit
import PlaygroundSupportPlaygroundPage.current.needsIndefiniteExecution = true
URLCache.shared = URLCache(memoryCapacity: 0, diskCapacity: 0, diskPath: nil)DataManager.getTopAppsDataFromFileWithSuccess { (data) -> Void in// 1var json: Anydo {json = try JSONSerialization.jsonObject(with: data)} catch {print(error)PlaygroundPage.current.finishExecution()}guard let dictionary = json as? [String: Any] else {PlaygroundPage.current.finishExecution()}// 2guard let topApps = TopApps(json: dictionary) else {print("Error initializing object")PlaygroundPage.current.finishExecution()}// 3guard let firstItem = topApps.feed?.entries?.first else {print("No such item")PlaygroundPage.current.finishExecution()}print(firstItem)PlaygroundPage.current.finishExecution()
}
- 首先和之前一样,调用 JSONSerialization 反序列化数据。
- 然后,用这个 JSON 对象创建一个 TopApps。
- 最后,调用我们创建的模型对象的 feed 属性的 entries 属性,获得第一个 app.
这就是全部我们需要做的工作!
保存文件,可以看到我们成功地拿到了 app 名字,但这次的办法要优雅得多了:
App(name: "Game of War - Fire Age", link: "https://itunes.apple.com/us/app/game-of-war-fire-age/id667728512?mt=8&uo=2")
注意:如果在这里出现错误,说 topapps.json 不能打开,那可能是没有权限,请关闭开始项目,然后删除 derived data 文件夹中的内容。
我们已经知道如何解析本地数据——要怎样解析远程数据呢?
抓取远程 JSON
让这个项目更完美些。通常我们需要抓取远程数据而不是本地文件。我们可以用一个网络请求抓取 App Store 的排行榜。
打开 DataManager.swift ,在 DataManager 的实现之前定义一个 topAppURL:
let topAppURL = "https://itunes.apple.com/us/rss/topgrossingipadapplications/limit=25/json"
然后,在实现部分添加这个方法:
public class func getTopAppsDataFromItunesWithSuccess(success: @escaping ((_ iTunesData: Data) -> Void)) {//1loadDataFromURL(url: URL(string: topAppURL)!) { (data, error) -> Void in//2if let data = data {//3success(data)}}
}
这个方法和之前的方法很相似,但这次使用了 URLSession 从 iTunes 抓取数据。代码解释如下:
- 首先调用 loadDataFromURL 方法,这个方法有一个 URL 参数和一个完成闭包,闭包有一个 Data 对象。
- 用一个可空绑定确保 data 不为空。
- 将 data 传递到 success 闭包,和之前一样。
打开主 playground 文件,将这句 :
DataManager.getTopAppsDataFromFileWithSuccess { (data) -> Void in
替换为:
DataManager.getTopAppsDataFromItunesWithSuccess { (data) -> Void in
现在你可以从 iTunes 获得数据了。
保存文件,你可以查看当前最热门的游戏是什么了。我看到的仍然是 “Saga 糖果消除”。我真的喜欢糖果消除游戏。
App(name: "Candy Crush Saga", link: "https://itunes.apple.com/us/app/candy-crush-saga/id553834731?mt=8&uo=2")
你看到的可能和上面不同,因为苹果商店里的排行榜随时都在变。
通常人们不仅仅对排行榜的第一名感兴趣——他们会想了解整个排行榜的内容。你没有必要为此写码——只需要用这句:
topApps.feed?.entries
Gloss 的底层机制
如你所见,在解析 JSON 数据时 Gloss 很好用,但它的底层机制是怎样的呢?
<~~ 是一个自定义操作符,用来执行一系列 Decoder.decode 函数。Gloss 内置了对许多类型的解码支持:
- 简单类型 (Decoder.decode(key:))
- Decodable 模型对象 (Decoder.decode(decodableForKey:))
- 简单数组 (Decoder.decode(key:))
- Decodable 模型数组 (Decoder.decode(decodableArrayForKey:) )
- 枚举类型 (Decoder.decode(enumForKey:))
- 枚举数组 (Decoder.decode(enumArrayForKey:) )
- URL 类型 (Decoder.decode(urlForKey:) )
- URL 数组 (Decode.decode(urlArrayForKey:))
在本教程中,你严重依赖 Decodable 模型。如果你需要更复杂的对象,你可以扩展 Decoder 并实现你自己的解码功能。
当然 Gloss 还能将对象转换回 JSON。如果你想了解这部分内容,请参考 Encodable 协议。
结束
这里下载完成的 playground 项目。
如果你更愿意采用本文前半部分描述的苹果的解析方式,你可以看一下苹果的在 Swift 中使用 JSON。它最终演示了一个如何编写使用 JSON 数据的网络层的实例非常有用。
但是,如果你更喜欢 Gloss 的方法,则需要密切关注它的新版本发布,因为它还在开发阶段。
希望你喜欢本教程,也请阅读本站的其它 Swift 教程。有任何问题和建议,请在下面留言!
Swift JSON 教程:使用 JSON相关推荐
- JSON教程(非常详细)
之前写过有关C语言JSON库:[C语言开源库]在Clion上使用开源库cJSON; JSON和XML的对比:JSON vs XML,为什么JSON更好? 下面就好好来了解一下JSON. 文章目录 JS ...
- JS中巧妙使用JSON教程
JS中巧妙使用JSON教程 1.JSON.parse() 2.JSON & Date 3.JSON.stringify() 1.JSON.parse() 我们可以使用 JSON.parse() ...
- 关于 JSON,什么是JSON
关于 JSON 2022-05-17 13:54 更新 JSON 教程 本 JSON 教程会帮助我们了解 JSON 以及如何在各种编程语言,比如 PHP,PERL,Python,Ruby,Java等等 ...
- python教程:Json模块中dumps、dump、loads、load函数用法讲解
1.json.dumps()和loads() json.dumps()将dict类型数据转成str. json.loads()刚好相反,将str类型的数据转成dict. import json dat ...
- SAP UI5 初学者教程之七 - JSON 模型初探试读版
Jerry 从 2014 年加入 SAP成都研究院 CRM Fiori 开发团队之后开始接触 SAP UI5,曾经在 SAP 社区和"汪子熙"微信公众号上发表过多篇关于 SAP U ...
- Swift 读取,处理Json数据
/*{"id": "001","name": "xyz","data": ["100&qu ...
- python生成json_Python JSON 教程
Python JSON 教程 本文我们通过示例学习python中解析.读取和写入json.同时也涉及到转换json至字典和格式化打印. 1. json 介绍 JSON (JavaScript Obje ...
- Python JSON 教程
Python JSON 教程 本文我们通过示例学习python中解析.读取和写入json.同时也涉及到转换json至字典和格式化打印. 1. json 介绍 JSON (JavaScript Obje ...
- json qbytearray 串 转_Qt之JSON教程-使用篇
以故事方式来学习如何使用Qt接口来操作JSON数据. JSON三兄弟 老大哥QJsonValue主要用于封装JSON值,类似于QVariant. 它能够存储以下值:与QVariant互转 QJsonV ...
最新文章
- java ResultSet常用操作
- --------------springMVC的开篇,以及底层执行流程,配置视图解析器,静态资源的访问,流程图,工作原理...
- DS5020配置集群存储
- 布局中文件中【控件间距参数详解以及单位选择】
- LeetCode35. 搜索插入位置(二分查找)
- 系统学习机器学习之SVM(一)
- Win7 旗舰版激活方法及密钥
- 64位驱动 hp630打印机_惠普630打印机驱动
- Reflector dll反编译工具
- 【Go语言】动态库和静态库详解
- 和尚挑水 java_用do...while语句编写程序t18_2.java
- 软件开发行业,年轻与大龄程序员的生存现状究竟如何?
- 1. 对输入的数组正序输出
- html病毒DropFileName,王国平博客-HTML 感染 DropFileName = “svchost.exe” Ramnit 蠕虫病毒 查杀解决办法...
- Redis应用---Redis可以用来做什么?
- 刘强东割袍弃兄弟,马爸爸醉心 996
- [iOS]Advanced Memory Management Programming Guide 高级内存管理编程指南(官方文档翻译)
- 爬虫 | 百行代码爬取14.5W条豆瓣图书信息
- java实验二_Java实验2
- 游戏建模师真的比公务员,事业编制单位还要“香”吗?