Swift 與 JSON 數(shù)據(jù)

我們大家平時(shí)在開發(fā) App 的時(shí)候,相信接觸最多的就是 JSON 數(shù)據(jù)了。只要你的 App 有讀取網(wǎng)絡(luò)數(shù)據(jù)的功能,你就免不了要與 JSON 打交道。比如你做一個(gè)新聞 App,你要讀取和解析新聞數(shù)據(jù),這樣才能顯示給用戶。

那么我們今天就來了解一下 JSON 以及它在 App 中的應(yīng)用吧。

在前兩節(jié)我們會介紹 JSON 數(shù)據(jù)格式,如果您已經(jīng)對 JSON 比較了解了,那么也可以跳過前兩節(jié),繼續(xù)閱讀后面的內(nèi)容。

什么是 JSON

首先,JSON 的全稱叫做 JavaScript Object Notation ,翻譯成中文就是 JavaScript 對象表示法,是一種輕量級的數(shù)據(jù)交互格式。

JSON 數(shù)據(jù)分為三種形式,對象,數(shù)組,值。

對象是一個(gè)無序的“‘名稱/值’對”集合。一個(gè)對象以“{”(左括號)開始,“}”(右括號)結(jié)束。每個(gè)“名稱”后跟一個(gè)“:”(冒號);“‘名稱/值’ 對”之間使用“,”(逗號)分隔。

數(shù)組是值(value)的有序集合。一個(gè)數(shù)組以“[”(左中括號)開始,“]”(右中括號)結(jié)束。值之間使用“,”(逗號)分隔。

值(value)可以是雙引號括起來的字符串(string)、數(shù)值(number)、true、false、 null、對象(object)或者數(shù)組(array)。這些結(jié)構(gòu)可以嵌套。

下面是一個(gè)簡單的例子:

{
  "firstname": "San",
  "lastname" : "Zhang",
  "age": 21,
  "friends": ["Mark","Li"]
}

上面的數(shù)據(jù)示例,表示了這樣一個(gè)結(jié)構(gòu),首先我們的數(shù)據(jù)被一對大括號包圍,那么我們的數(shù)據(jù)就是 對象 類型,然后它里面有四個(gè)屬性,firstname,lastname,age,friends。 其中前兩個(gè)屬性 firstnamelastname 字符串類型,他們的值分別是 SanZhangage 屬性代表年齡,所以它的值一個(gè) Number 類型的 21

注意一下,字符串類型和數(shù)字類型的區(qū)別,字符串類型的值用一對雙引號括了起來,而數(shù)值類型不需要雙引號。

最后,friends 屬性的值是一個(gè)數(shù)組,用一對中括號包圍起來,而數(shù)組中的元素,仍然是字符串類型。

以上就是 JSON 的一個(gè)基本結(jié)構(gòu),關(guān)于 JSON 更詳細(xì)的介紹,可以參看 json.org

JSON 數(shù)據(jù)實(shí)例

我們看完了 JSON 的格式之后,那么我們就接著看一下具體的 JSON 數(shù)據(jù)是怎樣的格式呢?

比如這個(gè)天氣數(shù)據(jù)接口: http://api.openweathermap.org/data/2.5/weather?q=China,bj&lang=zh_cn

如果我們在瀏覽器打開這個(gè)地址,我們就可以看到類似這樣的數(shù)據(jù):

{
  "coord": {
    "lon": 116.4,
    "lat": 39.91
  },
  "weather": [
    {
      "id": 520,
      "main": "Rain",
      "description": "陣雨",
      "icon": "09d"
    },
    {
      "id": 701,
      "main": "Mist",
      "description": "薄霧",
      "icon": "50d"
    }
  ],
  "base": "stations",
  "main": {
    "temp": 300.39,
    "pressure": 1008,
    "humidity": 94,
    "temp_min": 297.15,
    "temp_max": 303.71
  },
  "visibility": 2300,
  "wind": {
    "speed": 1,
    "deg": 140
  },
  "clouds": {
    "all": 75
  },
  "dt": 1437281131,
  "sys": {
    "type": 1,
    "id": 7405,
    "message": 0.0136,
    "country": "CN",
    "sunrise": 1437253268,
    "sunset": 1437305986
  },
  "id": 1816670,
  "name": "Beijing",
  "cod": 200
}

我們來簡單看一下,數(shù)據(jù)整體使用一對大括號包圍的,也就是說返回給我們的數(shù)據(jù),是一個(gè) JSON 對象 緊接著,這個(gè)對象包含了 coord 屬性,這個(gè)屬性的值又是一個(gè)對象,里面有兩個(gè)屬性 'lon' 和 'lat' 代表地理位置,后面還有很多其他屬性代表天氣的數(shù)據(jù)。

JSON 數(shù)據(jù)格式,可以很結(jié)構(gòu)化的表示出天氣的信息。而且數(shù)據(jù)結(jié)構(gòu)一目了然,非常的清晰。并且有很多在線工具可以幫助大家更好的編輯和查看 JSON 數(shù)據(jù)。
比如 http://www.jsoneditoronline.org

Swift 中處理 JSON 數(shù)據(jù)

我們在了解過 JSON 數(shù)據(jù)后,就繼續(xù)我們的主題吧。

使用 NSJSONSerialization

Swift 中處理 JSON 數(shù)據(jù)方式有很多種。首先,由于 Swift 可以引用 Cocoa 原生庫,所以我們可以用 Cocoa 中的 NSJSONSerialization 來處理 JSON 數(shù)據(jù),這個(gè)類也很好理解,它會將 JSON 數(shù)據(jù),轉(zhuǎn)換成 Cocoa 中的 NSDictionaryNSArray。我們來看一下如何用 NSJSONSerialization 來處理:

let APIURL = "http://api.openweathermap.org/data/2.5/weather?q=China,bj&lang=zh_cn"

if let url = NSURL(string: APIURL) {

    if let jsonData = NSData(contentsOfURL: url) {

        if let jsonObj:NSDictionary = NSJSONSerialization.JSONObjectWithData(jsonData, options: NSJSONReadingOptions.allZeros, error: nil) as? NSDictionary{

            if let weathers:NSArray = jsonObj["weather"] as? NSArray {

                var weatherSummary = "北京天氣情況:"

                for weather in weathers {

                    if let desc:String = weather["description"] as? String {

                      weatherSummary += desc + " "

                    }

                }

                print(weatherSummary)

            }

        }

    }

}

讓我們來逐個(gè)講解。

  1. 首先,我們通過 let url = NSURL(string: APIURL) 來講天氣接口包裝成 NSURL。

  2. 然后,我們使用 let jsonData = NSData(contentsOfURL: url) 將這個(gè) URL 的內(nèi)容讀取下載,存放到 NSData 中。

  3. 接下來,我們就要使用 NSJSONSerialization 將這些數(shù)據(jù)解析成 JSON 了。
    let jsonObj:NSDictionary = NSJSONSerialization.JSONObjectWithData(jsonData, options: NSJSONReadingOptions.allZeros, error: nil) as? NSDictionary
    這里我們 JSONObjectWithData 方法將傳入的 NSData 數(shù)據(jù)解析成 JSON 對象,如果我們的 JSON 根節(jié)點(diǎn)是以對象形式存放的,那么我們得到的就是一個(gè) NSDictionary。而如果是以數(shù)組形式存放的,那么我們得到的就是一個(gè) NSArray 了。后面還有兩個(gè)參數(shù) options 代表 JSON 讀取選項(xiàng),這個(gè)我們稍后會講到,error 參數(shù)表示 JSON 讀取中的錯(cuò)誤,如果傳入 nil 表示不接受錯(cuò)誤消息。

  4. 我們得到了解析出來的 JSON 后,我們就可以像訪問普通集合對象那樣得到里面的信息了:

if let weathers:NSArray = jsonObj["weather"] as? NSArray {

    var weatherSummary = "北京天氣情況:"

    for weather in weathers {

        if let desc:String = weather["description"] as? String {

          weatherSummary += desc + " "

        }

    }

    print(weatherSummary)

}

我們這里將天氣情況讀取出來,并打印到屏幕上,以我們上面的數(shù)據(jù)為例,打印到屏幕上就是這個(gè)樣子:

北京天氣情況:陣雨 薄霧

NSJSONSerialization 的讀取選項(xiàng)

就在剛剛,我們使用 NSJSONSerialization 成功的解析了 JSON 數(shù)據(jù),覺得用起來很爽吧。仔細(xì)回想一下,我們剛才還注意到有一個(gè) options 參數(shù)我們沒有詳細(xì)介紹。這個(gè)我們可以把它叫做讀取選項(xiàng),這個(gè)參數(shù)的類型是 NSJSONReadingOptions,它的取值可以是以下幾種:

  1. MutableContainers: 讓返回的 JSON 數(shù)據(jù)中的數(shù)組和字典是可更改的。
  2. AllowFragments: 允許 JSON 返回的數(shù)據(jù)有多個(gè)根節(jié)點(diǎn)。
  3. MutableLeaves: 使 JSON 返回的字符串是可更改的。

相信部分膽大心細(xì)的朋友會發(fā)現(xiàn)。。。

我靠,這說的都是什么那,我還是不明白!

所以。。客官莫急,聽我一一道來。

  • MutableContainers

首先,MutableContainers 這個(gè)選項(xiàng)就讓返回的 JSON 集合可更改,讓我們來看一個(gè)例子就一目了然了:

var jsonString:NSString = "{\"names\":[\"James\",\"Jobs\",\"Tom\"]}" as NSString
let jsonData = jsonString.dataUsingEncoding(NSUTF8StringEncoding)

if let jsonObj:NSDictionary = NSJSONSerialization.JSONObjectWithData(jsonData!, options: NSJSONReadingOptions.MutableContainers, error: nil) as? NSDictionary {

    //操作之前
    print(jsonObj) //James, Jobs, Tom

    if let nameArray:NSMutableArray = jsonObj["names"] as? NSMutableArray {

        nameArray.addObject("Cook")

    }

    //操作之后
    print(jsonObj) //James, Jobs, Tom, Cook

}

我們看一下吧,上面的代碼,我們在 JSONObjectWithData 方法調(diào)用的時(shí)候,加入了 NSJSONReadingOptions.MutableContainers 讀取參數(shù), 這樣一來我們就可以更改我們的結(jié)果集了,我們注意到上面的這段代碼:

if let nameArray:NSMutableArray = jsonObj["names"] as? NSMutableArray {

       nameArray.addObject("Cook")

}

取得 names 數(shù)組,并在里面增加一個(gè)新的項(xiàng)。隨后我們再次打印 jsonObj 對象,這次顯示的結(jié)果就是我們更改過的了。

如果我們在調(diào)用 JSONObjectWithData 方法的時(shí)候?qū)?NSJSONReadingOptions.MutableContainers 選項(xiàng)去掉的話,我們就不能更改這里面任何數(shù)組的元素了。

第一個(gè)選項(xiàng) MutableContainers 我們看完啦。 我們繼續(xù)

  • AllowFragments

還有另外一個(gè)選項(xiàng)參數(shù),就是 AllowFragments 這個(gè)參數(shù)的官方解釋是允許被解析的 JSON 數(shù)據(jù)的根層級,不是數(shù)組和對象。

額。。 聽起來怪怪的不好理解是吧。

這個(gè)選項(xiàng)確實(shí)容易引起歧義,包括他的名稱 AllowFragments,翻譯成中文叫允許碎片, 什么叫允許碎片呢,我再自己實(shí)踐研究過這個(gè)之前我一直是這么認(rèn)為的。。。

AllowFragments 的意思,是不是可以解析這樣的 JSON?

{"name":"Jobs"},{"name":"Ive"}

一段時(shí)間以來我的思維里是這么想的。可惜完全不是那么回事兒,如果你將這樣的 JSON 數(shù)據(jù)傳給 JSONObjectWithData 方法,你將得到一個(gè)無情的解析錯(cuò)誤。。。

那么,這東西到底是干什么用的呀~

其實(shí)官方文檔上面說的清清楚楚,可以讓跟節(jié)點(diǎn)不是對象或者數(shù)組。在 JSON 中只有三種類型,對象,數(shù)組,值。

其實(shí)說白了就是這樣,允許你的 JSON 數(shù)據(jù)是一個(gè)字面值,比如字符串,數(shù)字,等等。

比如我們可以傳入一個(gè)原始的字符串

"something wrong about api"

這種數(shù)據(jù),如果你打開了 AllowFragments, 是完全可以正常解析的(注意兩邊的雙引號,這個(gè)也包含在返回的數(shù)據(jù)中)。而如果你沒有打開這個(gè)選項(xiàng),對于這種數(shù)據(jù)就會解析失敗了。

var jsonFragmentString = "\"something wrong about api\"" as NSString

let jsonFragmentData = jsonFragmentString.dataUsingEncoding(NSUTF8StringEncoding)

if let jsonObj: AnyObject = NSJSONSerialization.JSONObjectWithData(jsonFragmentData!, options: .AllowFragments, error: nil) {

    //使用 AllowFragments 選項(xiàng),解析成功。
    print(jsonObj)

}

看完上面的代碼,相信大家瞬間就明白了,原來這家伙是做這個(gè)用的。

  • ** MutableLeaves **
    MutableLeaves 選項(xiàng),這個(gè)選項(xiàng)讓我一直百思不得其解,文檔上說,使用了這個(gè)選項(xiàng)后,所有對象的葉子節(jié)點(diǎn)的字符串屬性,都會變成 NSMutableString,而我試遍多種文檔,也未驗(yàn)證出來,得到的字符串依然是 NSString 而不是 NSMutableString

使用 NSJSONSerialization 創(chuàng)建 JSON 數(shù)據(jù)

剛才我們了解到如何用 NSJSONSerialization 來解析數(shù)據(jù)。同樣的,我們還可以使用 NSJSONSerialization 來構(gòu)建 JSON 數(shù)據(jù)。

讓我們看一下下面的代碼:

let names = ["Jobs","Cook","Ive"]

if let jsonData = NSJSONSerialization.dataWithJSONObject(names, options: NSJSONWritingOptions.allZeros, error: nil) {
    let jsonString = NSString(data: jsonData, encoding: NSUTF8StringEncoding)
    // ["Jobs","Cook","Ive"]
}

我們使用 dataWithJSONObject 方法將 JSON 對象轉(zhuǎn)換成 JSON 數(shù)據(jù),我們傳入的對象可以是數(shù)組也可以是字典,分別對應(yīng)了 JSON 中的數(shù)組和對象。

我們注意到 dataWithJSONObject 這個(gè)方法也有個(gè) options 選項(xiàng),它用來控制構(gòu)建 JSON 時(shí)的選項(xiàng),類型為 NSJSONWritingOptions。它只有一個(gè)選項(xiàng),就是 NSJSONWritingOptions.PrettyPrinted

這個(gè)選項(xiàng)的作用也不言而喻,就是讓生成的 JSON 數(shù)據(jù)是良好的格式化的:

let jsonObj = ["name":"Jobs","friends":["Ive","Cook"]]

if let jsonData = NSJSONSerialization.dataWithJSONObject(jsonObj, options: NSJSONWritingOptions.PrettyPrinted, error: nil) {

    let jsonString = NSString(data: jsonData, encoding: NSUTF8StringEncoding)

}

使用 PrettyPrinted 選項(xiàng),我們輸出的 JSON 就是這樣一個(gè)良好格式化的:

{
  "name": "Jobs",
  "friends": [
    "Ive",
    "Cook"
  ]
}

如果我們沒有使用這個(gè)選項(xiàng),那么我們得到的輸出就是這樣:

{"name": "Jobs","friends": ["Ive","Cook"]}

區(qū)別就在這,這下明白了吧。

其他方案

當(dāng)然了,除了使用原生的 NSJSONSerialization 來處理 JSON,我們還可以使用很多第三方庫來進(jìn)行 JSON 數(shù)據(jù)的操作,比如:SwiftyJSON, Argo

限于篇幅以及大家的閱讀疲勞期,我們這篇文章只介紹原生的方式。后續(xù)還會為大家更詳細(xì)的講解這些第三方庫的運(yùn)用,以及他們的好處及缺點(diǎn)。

下面是本篇文章對應(yīng)的 playground 文件,大家下載下來應(yīng)該應(yīng)該能作為更好的參考:

swift-json.playground

更多精彩內(nèi)容可關(guān)注微信公眾號:
swift-cafe

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,973評論 19 139
  • 國家電網(wǎng)公司企業(yè)標(biāo)準(zhǔn)(Q/GDW)- 面向?qū)ο蟮挠秒娦畔?shù)據(jù)交換協(xié)議 - 報(bào)批稿:20170802 前言: 排版 ...
    庭說閱讀 11,178評論 6 13
  • 越怕什么就越要去做它。
    Cheer迷路的西蘭花閱讀 374評論 0 0
  • 學(xué)校去了南京龍泉山莊體驗(yàn)農(nóng)家樂的生活,那里地段倒好,有油菜花、有野菜,有山坡、有湖水。最后還體驗(yàn)了一次摘草莓。 對...
    晏姝閱讀 618評論 0 0
  • “誰去拿快遞?幫我?guī)е ?”誰去吃飯?幫我?guī)е ?閨蜜打電話問我:“XX結(jié)婚,你說我去不去?平時(shí)沒什么交集,在...
    陽臺姑娘閱讀 417評論 0 4