原文:Swift JSON Tutorial: Working with JSON
作者:Luke Parham
译者:kmyhy

2017-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 中很简单:

  1. 从这里下载 Gloss 源代码 zip 包。
  2. 解压缩,然后将 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()
}
  1. 首先和之前一样,调用 JSONSerialization 反序列化数据。
  2. 然后,用这个 JSON 对象创建一个 TopApps。
  3. 最后,调用我们创建的模型对象的 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 抓取数据。代码解释如下:

  1. 首先调用 loadDataFromURL 方法,这个方法有一个 URL 参数和一个完成闭包,闭包有一个 Data 对象。
  2. 用一个可空绑定确保 data 不为空。
  3. 将 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相关推荐

  1. JSON教程(非常详细)

    之前写过有关C语言JSON库:[C语言开源库]在Clion上使用开源库cJSON; JSON和XML的对比:JSON vs XML,为什么JSON更好? 下面就好好来了解一下JSON. 文章目录 JS ...

  2. JS中巧妙使用JSON教程

    JS中巧妙使用JSON教程 1.JSON.parse() 2.JSON & Date 3.JSON.stringify() 1.JSON.parse() 我们可以使用 JSON.parse() ...

  3. 关于 JSON,什么是JSON

    关于 JSON 2022-05-17 13:54 更新 JSON 教程 本 JSON 教程会帮助我们了解 JSON 以及如何在各种编程语言,比如 PHP,PERL,Python,Ruby,Java等等 ...

  4. python教程:Json模块中dumps、dump、loads、load函数用法讲解

    1.json.dumps()和loads() json.dumps()将dict类型数据转成str. json.loads()刚好相反,将str类型的数据转成dict. import json dat ...

  5. SAP UI5 初学者教程之七 - JSON 模型初探试读版

    Jerry 从 2014 年加入 SAP成都研究院 CRM Fiori 开发团队之后开始接触 SAP UI5,曾经在 SAP 社区和"汪子熙"微信公众号上发表过多篇关于 SAP U ...

  6. Swift 读取,处理Json数据

    /*{"id": "001","name": "xyz","data": ["100&qu ...

  7. python生成json_Python JSON 教程

    Python JSON 教程 本文我们通过示例学习python中解析.读取和写入json.同时也涉及到转换json至字典和格式化打印. 1. json 介绍 JSON (JavaScript Obje ...

  8. Python JSON 教程

    Python JSON 教程 本文我们通过示例学习python中解析.读取和写入json.同时也涉及到转换json至字典和格式化打印. 1. json 介绍 JSON (JavaScript Obje ...

  9. json qbytearray 串 转_Qt之JSON教程-使用篇

    以故事方式来学习如何使用Qt接口来操作JSON数据. JSON三兄弟 老大哥QJsonValue主要用于封装JSON值,类似于QVariant. 它能够存储以下值:与QVariant互转 QJsonV ...

最新文章

  1. java ResultSet常用操作
  2. --------------springMVC的开篇,以及底层执行流程,配置视图解析器,静态资源的访问,流程图,工作原理...
  3. DS5020配置集群存储
  4. 布局中文件中【控件间距参数详解以及单位选择】
  5. LeetCode35. 搜索插入位置(二分查找)
  6. 系统学习机器学习之SVM(一)
  7. Win7 旗舰版激活方法及密钥
  8. 64位驱动 hp630打印机_惠普630打印机驱动
  9. Reflector dll反编译工具
  10. 【Go语言】动态库和静态库详解
  11. 和尚挑水 java_用do...while语句编写程序t18_2.java
  12. 软件开发行业,年轻与大龄程序员的生存现状究竟如何?
  13. 1. 对输入的数组正序输出
  14. html病毒DropFileName,王国平博客-HTML 感染 DropFileName = “svchost.exe” Ramnit 蠕虫病毒 查杀解决办法...
  15. Redis应用---Redis可以用来做什么?
  16. 刘强东割袍弃兄弟,马爸爸醉心 996
  17. [iOS]Advanced Memory Management Programming Guide 高级内存管理编程指南(官方文档翻译)
  18. 爬虫 | 百行代码爬取14.5W条豆瓣图书信息
  19. java实验二_Java实验2
  20. 游戏建模师真的比公务员,事业编制单位还要“香”吗?

热门文章

  1. 护卫神mysql提权_护卫神主机大师提权漏洞利用分析
  2. Matplotlib绘图从零入门到实践(含各类用法详解)
  3. postgresql 分词_使用Postgresql进行中文分词
  4. HTML embed标签使用方法和属性详解
  5. 网页嵌入Bilibili HTML5视频播放
  6. Lucene基本语法
  7. Android 13 返回导航大变更,返回键彻底废弃 + 可预见型返回手势
  8. 第一个Ionic项目
  9. 【接口回调】关于接口回调的理解
  10. 模拟乌龟吃鱼的小游戏