之前的文章使用Ono讀取XML文件/簡(jiǎn)書介紹了如何處理XML數(shù)據(jù),隨著Web時(shí)代的ajax的盛行,在App時(shí)代,另一個(gè)最常需要處理的的數(shù)據(jù)就是JSON數(shù)據(jù),尤其現(xiàn)在的Restful背景下API數(shù)據(jù)的傳遞,更有勝者直接就是JSON RPC。此時(shí)既要讀取解析JSON也要為數(shù)據(jù)結(jié)構(gòu)生成對(duì)應(yīng)的JSON數(shù)據(jù)。在以前,iOS/Mac平臺(tái)有多鐘JSON解決方案,如SBJSON、TouchJSON、YAJL、JSONKit,當(dāng)然還有C里面的cJSON/RapidJSON可以進(jìn)行封裝,效率和正確率也是剛剛的。Apple自家也提供了一套解決方案:NSJSONSerialization,這個(gè)NSJSONSerialization比他們自家的XML解析工具NSXMLParser可好多了,他既有可人的接口,又提供了非凡的效率(有測(cè)評(píng)表明比SBJSON/JSONKit等強(qiáng)),并且是Apple系統(tǒng)提供的正規(guī)軍,因此首推這個(gè)方案。
NSJSONSerialization提供的接口非常簡(jiǎn)單,甚至于官網(wǎng)的Manual只有少量的文字描述。
You use the NSJSONSerialization class to convert JSON to Foundation objects and convert Foundation objects to JSON
可見(jiàn),NSJSONSerialization是可以直接將JSON數(shù)據(jù)轉(zhuǎn)換成Foundation中提供的對(duì)象如NSArray、NSDictionary、NSString、NSNumber等;也可以直接把這些Foundation的對(duì)象轉(zhuǎn)換成JSON數(shù)據(jù),這一看就是個(gè)超人性的接口。
但是這么好的接口也有些限制,他要求被解析的JSON數(shù)據(jù)必須是
- 頂級(jí)對(duì)象需要是一個(gè){}表示的字典或者[]表示的數(shù)組,因此也只能對(duì)NSArray/NSDictionary做序列化。(這點(diǎn)感覺(jué)基本都是這么用的)
- 所有的節(jié)點(diǎn)數(shù)據(jù)必須要能被解析成 NSString, NSNumber, NSArray, NSDictionary 或者 NSNull.反之也只能對(duì)這些對(duì)象做序列化。
- 字典的Key必須是NSString表達(dá)的類型。
- 數(shù)字類型的數(shù)不能是NaN或者無(wú)限大。
總結(jié)來(lái)看其實(shí)只要我們:
在序列化的時(shí)候,只要是對(duì)NSArray或者NSDictionary對(duì)象序列化,并且其單元組成只能是 NSString, NSNumber(NaN/無(wú)限大不可以), NSArray, NSDictionary(key必須是NSString) 或者 NSNull即可。也可以通過(guò)
+ (BOOL)isValidJSONObject:(id)obj
這個(gè)類方法來(lái)判斷其是否可以被正確序列化。
1. 解析JSON數(shù)據(jù)到Foundation對(duì)象
NSJSONSerialization提供了兩個(gè)方法來(lái)解析JSON數(shù)據(jù),一個(gè)是從NSData里面取數(shù)據(jù),另一個(gè)是從NSInputStream 輸入流中取數(shù)據(jù),從而方便對(duì)File/Sokcet的操作。數(shù)據(jù)的格式必須是UTF-8, UTF-16LE, UTF-16BE, UTF-32LE, UTF-32BE中的一種,是否帶有BOM都可以。
+ (nullable id)JSONObjectWithData:(NSData *)data options:(NSJSONReadingOptions)opt error:(NSError **)error;
+ (nullable id)JSONObjectWithStream:(NSInputStream *)stream options:(NSJSONReadingOptions)opt error:(NSError **)error;
兩個(gè)函數(shù)不同的地方就是第一個(gè)參數(shù),一個(gè)傳入的是NSData一個(gè)傳入的是NSInputStream.
options參數(shù)控制讀入的策略:
- NSJSONReadingMutableContainers : 讀出來(lái)的對(duì)象放入到NSMutableArray或者NSMutableDictionary中,默認(rèn)是放到NSArray/NSDictionary中。這個(gè)選項(xiàng)在我們讀取一段JSON然后進(jìn)行追加或者刪除節(jié)點(diǎn)的時(shí)候,非常適合,修改下Array或者Dictionary成員,然后在序列化就可以了。
- NSJSONReadingMutableLeaves: 讀出來(lái)的每個(gè)節(jié)點(diǎn)對(duì)象的String放入到NSMutableString中,這樣就為修改節(jié)點(diǎn)提供了可能,在讀入后修改節(jié)點(diǎn)再序列化回去的場(chǎng)景比較適用。
- NSJSONReadingAllowFragments : 正常情況JSON的頂級(jí)節(jié)點(diǎn)要么是Array[]要么是Dictionary{}。如果JSON數(shù)據(jù)不是通過(guò){}/[]表示的dictionary或者array的片段數(shù)據(jù),這用這個(gè)選項(xiàng)進(jìn)行指定。
error參數(shù)是Cocoa API常用的出錯(cuò)方式,傳入一個(gè)NSError **,如果出錯(cuò),通過(guò)這個(gè)值返回回來(lái)。
解析回來(lái)以后就可以按照NSArray或者NSDictionary的方式進(jìn)行使用了,特別方便。比如:
{
"id": 1,
"name": "hanmeimei",
"class": [
"English",
"Mathematics"
]
}
相當(dāng)于:
NSArray *myClass = @[@"English",@"Mathematics"];
NSDictionary *student = @{@"id":@1, @"name":@"hmeimei", @"class": myClass};
解析出來(lái)就是一個(gè) student.
2. 將Foundation對(duì)象序列化到JSON中
與上面對(duì)應(yīng)的NSJSONSerialization也提供了兩個(gè)將對(duì)象序列化到JSON的接口,一個(gè)是序列化到NSData中,一個(gè)是序列化到NSOutputStream中,方便對(duì)File/Socket的操作,序列化的結(jié)果是UTF-8格式編碼數(shù)據(jù)。
+ (nullable NSData *)dataWithJSONObject:(id)obj options:(NSJSONWritingOptions)opt error:(NSError **)error;
+ (NSInteger)writeJSONObject:(id)obj toStream:(NSOutputStream *)stream options:(NSJSONWritingOptions)opt error:(NSError **)error;
兩個(gè)函數(shù)不同的地方就是第一個(gè)參數(shù),一個(gè)傳入的是NSData一個(gè)傳入的是NSOutputStream.
默認(rèn)情況下,生成的JSON是盡量壓縮后的結(jié)果,去掉了不必要的空格,可讀性比較差。如果希望生成可讀性較好的格式,比如在調(diào)試的階段,可以使用NSJSONWritingPrettyPrinted
的option參數(shù)為輸出的格式加上必要空格,進(jìn)行格式化。
同樣,如果序列化過(guò)程中出錯(cuò),通過(guò)error參數(shù)進(jìn)行返回。在進(jìn)行序列化的時(shí)候可以使用
+ (BOOL)isValidJSONObject:(id)obj;
判斷是否可以正常序列化。
比如上面的對(duì)student執(zhí)行序列化話,就可以得到上面的JSON數(shù)據(jù)。
3. 一個(gè)示例
最后通過(guò)一個(gè)示例程序看下基本的調(diào)用流程。
JSON文件為students.json:
[
{
"id": 1,
"name": "hanmeimei",
"class": [
"English",
"Mathematics"
]
},
{
"id": 2,
"name": "lilei",
"class": [
"English",
"Mathematics"
]
}
]
程序主要代碼:
NSInputStream *ifs = [[NSInputStream alloc] initWithFileAtPath:@"your_file_path/students.json"];
if (nil == ifs) {
NSLog(@"ifs is nil");
return -1;
}
[ifs open];
NSError *error = nil;
NSMutableArray *students = [NSJSONSerialization JSONObjectWithStream:ifs options:NSJSONReadingMutableContainers error:&error];
if (nil != error) {
NSLog(@"Unparse JSON Error");
}
for (NSDictionary *student in students) {
NSLog(@"student: %@", [student objectForKey:@"name"]);
NSLog(@" select class:");
for (NSString *cls in [student objectForKey:@"class"]) {
NSLog(@" %@\\n", cls);
}
}
// Parse To
NSDictionary *xiaowang =@{@"id":@3, @"name":@"xiaowang", @"class": @[@"none"]};
[students addObject:xiaowang];
error = nil;
NSData *jsonBuf = [NSJSONSerialization dataWithJSONObject:students options:NSJSONWritingPrettyPrinted error:&error];
NSString *jsonStr = [[NSString alloc] initWithData:jsonBuf encoding:NSUTF8StringEncoding];
NSLog(@"JSON is %@", jsonStr);
輸出為:
student: hanmeimei
select class:
English
Mathematics
student: lilei
select class:
English
Mathematics
JSON is [
{
"id" : 1,
"name" : "hanmeimei",
"class" : [
"English",
"Mathematics"
]
},
{
"id" : 2,
"name" : "lilei",
"class" : [
"English",
"Mathematics"
]
},
{
"id" : 3,
"name" : "xiaowang",
"class" : [
"none"
]
}
]
可以看到加空格的格式化效果還是比較明顯的。