如果 app 需要有网络功能并且有一个后端服务器处理和返回数据的话,那么现在基本上要和 JSON 打交道是没跑儿了的。在 Swift 里处理 JSON 其实是一件挺棘手的事情,因为 Swift 对于类型的要求非常严格,所以在解析完 JSON 之后想要从结果的 AnyObject 中获取某个键值是一件非常麻烦的事情。举个例子,我们使用 NSJSONSerialization 解析完一个 JSON 字符串后,得到的是 AnyObject?

// jsonString
{"menu": {
    "id": "file",
    "value": "File",
    "popup": {
        "menuitem": [
            {"value": "New", "onclick": "CreateNewDoc()"},
            {"value": "Open", "onclick": "OpenDoc()"},
            {"value": "Close", "onclick": "CloseDoc()"}
        ]
    }
}}
let jsonString = //...

let json: AnyObject = try! NSJSONSerialization.JSONObjectWithData(
    jsonString.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true)!,
    options: [])

我们如果想要访问 menu 里的 popup 中 第一个 menuitem 的 value 值的话,最正规的情况下,需要写这样的代码:

if let jsonDic = json as? NSDictionary {
    if let menu = jsonDic["menu"] as? [String: AnyObject] {
        if let popup: AnyObject = menu["popup"] {
            if let popupDic = popup as? [String: AnyObject] {
                if let menuItems: AnyObject = popupDic["menuitem"] {
                    if let menuItemsArr = menuItems as? [AnyObject] {
                        if let item0 = menuItemsArr[0]
                                        as? [String: AnyObject] {
                            if let value: AnyObject = item0["value"] {
                                print(value)
                            }
                        }
                    }
                }
            }
        }
    }
}
// 输出 New

什么?你难道把这段代码看完了?我都不忍心写下去了...如果你真的想要坚持这么做的话,我只能说呵呵,并且祝你好运了。

当然,在 Swift 1.2 中,我们可以在同一个 if let 语句中进行 unwrap,这样会比原来稍好一些,但是依旧十分麻烦:

if let jsonDic = json as? NSDictionary,
          menu = jsonDic["menu"] as? [String: AnyObject],
         popup = menu["popup"],
      popupDic = popup as? [String: AnyObject],
     menuItems = popupDic["menuitem"],
  menuItemsArr = menuItems as? [AnyObject],
         item0 = menuItemsArr[0] as? [String: AnyObject],
         value = item0["value"]
{
    print(value)
}

那么,我们应该怎么做呢?在上面的代码中,最大的问题在于我们为了保证类型的正确性,做了太多的转换和判断。我们并没有利用一个有效的 JSON 容器总应该是字典或者数组这个有用的特性,而导致每次使用下标取得的值都是需要转换的 AnyObject。如果我们能够重载下标的话,就可以通过下标的取值配合 ArrayDictionay 的 Optional Binding 来简单地在 JSON 中取值。鉴于篇幅,我们在这里不给出具体的实现。感兴趣的读者可以移步看看 json-swift 或者 SwiftyJSON 这样的项目,它就使用了重载下标访问的方式简化了 JSON 操作。使用这个工具,上面的访问可以简化为下面的类型安全的样子:

// 使用 SwiftJSON
if let value = JSON(json)["menu"]["popup"]["menuitem"][0]["value"].string {
    print(value)
}

这样就简单多了。