我們大家平時(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è)屬性 firstname
和 lastname
字符串類型,他們的值分別是 San
和 Zhang
。 age
屬性代表年齡,所以它的值一個(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 中的 NSDictionary
和 NSArray
。我們來看一下如何用 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è)講解。
首先,我們通過
let url = NSURL(string: APIURL)
來講天氣接口包裝成 NSURL。然后,我們使用
let jsonData = NSData(contentsOfURL: url)
將這個(gè) URL 的內(nèi)容讀取下載,存放到NSData
中。接下來,我們就要使用
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ò)誤消息。我們得到了解析出來的 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
,它的取值可以是以下幾種:
-
MutableContainers
: 讓返回的 JSON 數(shù)據(jù)中的數(shù)組和字典是可更改的。 -
AllowFragments
: 允許 JSON 返回的數(shù)據(jù)有多個(gè)根節(jié)點(diǎn)。 -
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)該能作為更好的參考:
更多精彩內(nèi)容可關(guān)注微信公眾號:
swift-cafe