然后,将 optional 初始化方法换成下面的初始化方法。当 JSON 参数中包含有空值时,这个初始化方法会抛出一个上面定义的错误。
public init(json: [String: Any]) throws { //1 guard 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") } //2 self.name = name self.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 中很简单:
项目导航器看起来是这个样子:
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] 的别名。
TopAppsTopApps 模型用于表示顶级对象,它只包含一个键值对:
{ "feed": { ... } }创建一个新的 Swift 文件,名为 TopApps.swift,保存到 playground 的 Sources 文件夹下。编辑它的代码为:
public struct TopApps: Decodable { // 1 public let feed: Feed? // 2 public init?(json: JSON) { feed = "feed" <~~ json } }首先要定义模型所用到的属性。这里我们只有一个属性。先别管 Feed 报出的异常,我们会在后面定义 Feed 模型类。
为了实现 Decodable 协议,TopApps 必须实现 optional 的初始化方法。
可能 <~~ 运算符有点陌生。它是 Encode 运算符,在 Gloss 的 Operators.swift 文件中定义。因为 Feed 也是一个 Decodable 类型,Gloss 可以把编码工作交给这个对象。
FeedFeed 对象和顶级对象很像。它有两个键值对,但由于我们只对上榜的 25 个 app 感兴趣,因此 author 对象其实是没有必要处理的。
{ "author": { ... }, "entry": [ ... ] }新建一个 Swift 文件,名为 Feed.swift,保存到 Sources 文件夹下:
public struct Feed: Decodable { public let entries: [App]? public init?(json: JSON) { entries = "entry" <~~ json } } AppApp 是最后一个模型对象,它表示一个 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 { // 1 public let name: String public let link: String public init?(json: JSON) { // 2 guard 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 = name self.link = link } }Feed 和 TopApps 都使用 optional 属性。但当我们确定 JSON 中某个值肯定存在时,可以用非 optional 的属性。
我们并不需要为 JSON 的每个成员都创建一个模型对象。例如,这里就没有为 in:name 和 id 创建模型对象。当我们使用非可空对象和嵌套对象时,一定要进行非空校验。
现在模型类已经准备好了,我们该让 Gloss 干活了!
打开 playground 文件,将它的内容替换为:
import UIKit import PlaygroundSupport PlaygroundPage.current.needsIndefiniteExecution = true URLCache.shared = URLCache(memoryCapacity: 0, diskCapacity: 0, diskPath: nil) DataManager.getTopAppsDataFromFileWithSuccess { (data) -> Void in // 1 var json: Any do { json = try JSONSerialization.jsonObject(with: data) } catch { print(error) PlaygroundPage.current.finishExecution() } guard let dictionary = json as? [String: Any] else { PlaygroundPage.current.finishExecution() } // 2 guard let topApps = TopApps(json: dictionary) else { print("Error initializing object") PlaygroundPage.current.finishExecution() } // 3 guard let firstItem = topApps.feed?.entries?.first else { print("No such item") PlaygroundPage.current.finishExecution() } print(firstItem) PlaygroundPage.current.finishExecution() }这就是全部我们需要做的工作!