2018-06-05 项目1:JSON解析器(自己实现)
一.项目要求
![](https://img.haomeiwen.com/i8407639/2a00dcfe15606b7a.png)
二.设计思路
整个程序的设计思路如下:
![](https://img.haomeiwen.com/i8407639/908405e0cfe6a066.png)
- 将一个NSArray放入到总解析器当中,当识别到为字典时,则使用字典解析器解析该字典,当识别到为字符时,则使用字符解析器解析该字符。
- 当字典解析器获得的Object里带有字符,则将Object传递给总解析器,等待字符的返回,放入字典中。
- 当字符解析器里的元素带有字典,则将该元素传递给总解析器,等待字典的返回后,放入数组中。
- 当总解析器获得了字典和字符的返回时,如果还没结束读取,则继续读取字符串,如果结束读取,则将返回结果返回主函数
三.代码实现
1.读取文件与处理
我将文件的每一个字符都读出来,并舍弃掉其中的换行符和空格,放进了一个数组dict里面
while (fgets(word, 200, jsonFile)) {
//获取文本内容
NSString *tmp = [NSString stringWithUTF8String: word];
[_jsonString appendString: tmp];
//去掉所有空格和换行
tmp = [tmp stringByReplacingOccurrencesOfString:@" " withString:@""];
tmp = [tmp stringByReplacingOccurrencesOfString:@"\n" withString:@""];
tmp = [tmp stringByReplacingOccurrencesOfString:@"\t" withString:@""];
tmp = [tmp stringByReplacingOccurrencesOfString:@"\r" withString:@""];
for (int i = 0; i < [tmp length]; i++) {
//转成一个个字符读取
NSString *subChar = [tmp substringWithRange:NSMakeRange(i, 1)];
[dict addObject: subChar];
}
}
2.字符解析器
字符解析器通过循环遍历每一个字符,定义两个伪指针begin和end,当begin和end相遇的时候,遍历结束。
字符解析器需要处理的数据有:String、int、float、bool、null、数组,通过传入的第一个字符来判断进入哪个处理步骤
//处理String
if ([beginChar isEqualToString: @"\""]) {
//从开始的指针找
begin++;
NSInteger quoteIndex = [array indexOfObject: @"\"" inRange: NSMakeRange(begin, end - begin + 1)];
if (quoteIndex == NSNotFound) {
//没找到引号
return nil;
}
//获得key
NSMutableString *key = [[NSMutableString alloc] init];
for (int i = begin; i < quoteIndex; i++) {
[key appendString: array[i]];
}
begin = (int) quoteIndex + 1;
return key;
}
处理null、true、false即判断其是否为预设的字符串。
else if ([beginChar isEqualToString: @"n"]) {
//处理null
if (begin + 3 <= end) {
if ([array[begin + 1] isEqualToString: @"u"] &&
[array[begin + 2] isEqualToString: @"l"] &&
[array[begin + 3] isEqualToString: @"l"]) {
begin += 4;
return [NSNull null];
}
else
return nil;
}
else
return nil;
}
else if ([beginChar isEqualToString: @"t"]) {
//处理true
if (begin + 3 <= end) {
if ([array[begin + 1] isEqualToString: @"r"] &&
[array[begin + 2] isEqualToString: @"u"] &&
[array[begin + 3] isEqualToString: @"e"]) {
begin += 4;
return [NSNumber numberWithBool: YES];
}
else
return nil;
}
else
return nil;
}
else if ([beginChar isEqualToString: @"f"]) {
//处理false
if (begin + 4 <= end) {
if ([array[begin + 1] isEqualToString: @"a"] &&
[array[begin + 2] isEqualToString: @"l"] &&
[array[begin + 3] isEqualToString: @"s"] &&
[array[begin + 4] isEqualToString: @"e"]) {
begin += 5;
return [NSNumber numberWithBool: NO];
}
else
return nil;
}
else
return nil;
}
处理数字的时候,需要用NSScanner判断数字是int还是float类型,然后再进行相应的操作。
//判断是否为整形:
- (BOOL)isPureInt:(NSString*)string{
NSScanner* scan = [NSScanner scannerWithString:string];
int val;
return[scan scanInt:&val] && [scan isAtEnd];
}
//判断是否为浮点形:
- (BOOL)isPureFloat:(NSString*)string{
NSScanner* scan = [NSScanner scannerWithString:string];
float val;
return[scan scanFloat:&val] && [scan isAtEnd];
}
else if ([numberArray containsObject: beginChar]) {
//处理数字字符串
NSMutableString *key = [[NSMutableString alloc] init];
while ([numberArray containsObject: beginChar] || [beginChar isEqualToString: @"."]) {
//获取数字字符串
[key appendString: beginChar];
begin++;
if (begin <= end) {
beginChar = [NSString stringWithString: array[begin]];
}
else
break;
}
//处理数字
if ([self isPureInt: key]) {
int value = [key intValue];
return [NSNumber numberWithInt: value];
}
else if ([self isPureFloat: key]){
float value = [key floatValue];
return [NSNumber numberWithFloat: value];
}
else {
return nil;
}
}
处理数组是最考验能力的,因为数组里面的逗号、花括号、方括号,都是截然不同的几种情况。
首先遇到数组的时候,需要判断两个指针是否相遇,当相遇的时候(即begin==end),则这次数组才是真正的处理完毕。
- 我对数组处理的思路如下:
获得每一个逗号前面的子数组/元素(如果是最后一个元素则直接判断是否达到终点),将其加入所求的result数组当中,直到到终止条件,返回该result数组。
但是有个问题,如何判断逗号是否属于这个数组呢?要知道,逗号有可能属于很多对象,比如说子数组、子子数组、子字典、子子字典…… - 解决方法:讨论每一种情况(逗号、子数组的分布、子字典的分布),去反馈出这个逗号到底属不属于这个数组,该不该加到result数组当中,如果不该加,则直接将子数组加到该数组当中去。
什么意思?来看看以下情况:
[a,[b,c],d]
很明显,第一个逗号和第三个逗号都需要加到result数组当中去,而第二个逗号是属于子数组[b,c]的,那么当我们判断出这个逗号不属于这个result数组的时候,我们就直接把整个[b,c]取出来,解析后(交给自己递归)返回一个结果,放进result数组里面。
再来个复杂点的情况就是:
{
"A": ["a", {
"c": 1,
"d": [2,3,4]
}, "e"]
}
在这里,第一个逗号和最后一个逗号的数组是result数组的,第二个逗号属于子字典,第三、四个逗号均属于子字典里面的子数组。很复杂对吧。
所以我们的解决方案就是,检索出每一种情况,对其进行相应的操作。情况的分布基于字典、数组和逗号的位置分布,我来列几种情况:
由于正确的json格式下,右括号一定在左括号右边,因此,在右方我们只判断右括号,在左方我们只判断左括号就OK了。
- 情况一:逗号属于result数组
//quote < left_bracket < left_brace
,[]{}
//right_bracket < quote < left_bracket
[],{}
//right_bracket < right_brace < quote
[]{},
//braceNotFound、right_bracket < quote
[],
//bracketNotFound、right_brace < quote
{},
- 情况二:逗号属于子字典
//left_brace < quote < right_brace < left_bracket
{,}[]
//left_brace < left_bracket < right_bracket < quote < right_brace
{[],}
- 情况三:逗号属于子数组
//left_bracket < quote < right_bracket < left_brace < right_brace
[,]{}
//left_bracket < left_brace < right_brace < quote < right_bracket
[{},]
那么其实我们要获得这五个元素的index进行相应的判断就可以了。但是这里要极其小心,因为要同时考虑截取的数组长度、指针位移距离等因素。
NSArray* subString = [array subarrayWithRange: NSMakeRange(begin, end - begin + 1)];
NSInteger quoteIndex = [subString indexOfObject: @","];
NSInteger leftBracketsIndex = [subString indexOfObject: @"["];
NSInteger leftBraceIndex = [subString indexOfObject: @"{"];
获取右括号的时候一定要注意是从当前位置获取,否则的话获取到的是其他数组的括号,就会报错。
- (int) findRightBracketIndex: (NSArray* ) array
{
int count = 0;
for (int i = 0; i < [array count]; i++) {
if ([array[i] isEqualToString: @"["])
count++;
if ([array[i] isEqualToString: @"]"]) {
count--;
//找到了最外层的数组
if (count == 0)
return i;
}
}
return -1;
}
当我们截取了一个子元素之后,还要再看看下一位是不是真的是逗号或者结束了。
//看看下一位是不是逗号或者结束
if ([array[begin] isEqualToString: @","] || [array[begin] isEqualToString: @"}"]) {
begin++;
}
处理完之后,我们应该会获得一个带元素的数组,这时候我们交由总解析器去继续解析:
id jsonResult = nil;
jsonResult = [self parseWithJsonStringArray: subArray];
if (jsonResult == nil)
return nil;
[result addObject: jsonResult];
当然,我们还得应对来自字典解析器给我们发来的字典请求,我们也交给总解析器去处理这件事情:
else if ([beginChar isEqualToString: @"{"]) {
id jsonResult = nil;
jsonResult = [self parseWithJsonStringDictionary: array];
if (jsonResult == nil)
return nil;
[result addObject: jsonResult];
return result;
}
3.字典解析
我们的字典是有格式要求的,必须为"key":value形式,其中,value可以为字典,也可以为元素。
首先来自总解析器的请求中,"key":的格式是要限制死的,我们通过寻找引号来一位位地读取出key,再次强调要注意指针的移动:
NSInteger quoteIndex = [array indexOfObject: @"\"" inRange: NSMakeRange(begin, end - begin + 1)];
if (quoteIndex == NSNotFound) {
//没找到引号
return nil;
}
for (int i = begin; i < quoteIndex; i++) {
[key appendString: array[i]];
}
//找到了key
//再次右移
begin = (int) quoteIndex + 1;
然后判断跟着的是否为冒号:
if ([beginChar isEqualToString: @":"])
begin++;
最后判断value是字典还是元素,如果是字典,则交由总解析器处理;如果是元素,则交由字符解析器去处理,最后返回一个result的NSDictionary类。
if ([beginChar isEqualToString: @"{"]) {
//是一个字典
NSMutableArray *father = [[NSMutableArray alloc] init];
father = [self parseWithJsonStringDictionary: subArray];
if (father == nil)
return nil;
[result setObject: father forKey: key];
}
else {
//是一个元素
NSMutableArray *father = [[NSMutableArray alloc] init];
father = [self parseWithJsonStringArray: subArray];
if (father == nil)
return nil;
[result setObject: father forKey: key];
}
4.总解析器
总解析器的作用是根据自己想要处理的字符,分成一个个的单独的字典,交由字典解析器调用。调用完毕得到返回的数据之后,将其存入result数组里面。
首先判断是不是一个合格的{}格式
if (![beginChar isEqualToString: @"{"] || ![endChar isEqualToString: @"}"])
return nil;
然后又进行上面讨论过的各种判断,加入到result数组当中,交由字典解析器处理。
id jsonResult = nil;
jsonResult = [self parseDictionary: subDictionary];
if (jsonResult == nil)
return nil;
[result addObject: jsonResult];
最后在返回里面判断Array的count,如果为1则只有一个字典,返回字典,否则返回数组
if ([result count] > 1)
return result;
else
return result[0];
最终在main函数调用就OK啦,和上一次的博客一样的。
四.测试样例
正确的输入样例:
//样例1
{
"A": {
"B": {
"C": 1
},
"D": ["a", [true, false], "b"]
},
"E": 2,
"F":{
"G":[6.1,7.23,8.45]
}
}
//输出1
The result dictionary is (
{
A = (
{
B = {
C = 1;
};
},
{
D = (
a,
(
1,
0
),
b
);
}
);
},
{
E = 2;
},
{
F = {
G = (
"6.1",
"7.23",
"8.45"
);
};
}
)
//样例2
{
"name": "中国",
"province": [{
"name": "黑龙江",
"cities": {
"city": ["哈尔滨", "大庆"]
}
}, {
"name": "广东",
"cities": {
"city": ["广州", "深圳", "珠海"]
}
}, {
"name": "台湾",
"cities": {
"city": ["台北", "高雄"]
}
}, {
"name": "新疆",
"cities": {
"city": ["乌鲁木齐"]
}
}]
}
//输出2
The result dictionary is (
{
name = "中国";
},
{
province = (
(
(
{
name = "黑龙江";
},
{
cities = {
city = (
"哈尔滨",
"大庆"
);
};
}
)
),
(
(
{
name = "广东";
},
{
cities = {
city = (
"广州",
"深圳",
"珠海"
);
};
}
)
),
(
(
{
name = "台湾";
},
{
cities = {
city = (
"台北",
"高雄"
);
};
}
)
),
(
(
{
name = "新疆";
},
{
cities = {
city = (
"乌鲁木齐"
);
};
}
)
)
);
}
)
//样例3(很多很多的子数组)
{
"A":[1,[2,[[3]],4],[[[5]]]]
}
//输出3
The result dictionary is {
A = (
1,
(
2,
(
(
3
)
),
4
),
(
(
(
5
)
)
)
);
}
//样例4(很多子字典)
{
"A": {
"B": {
"C": 1
},
"D": [
[
[
[1]
]
]
]
}
}
//输出4
The result dictionary is {
A = (
{
B = {
C = 1;
};
},
{
D = (
(
(
(
1
)
)
)
);
}
);
}
错误的输入样例:
//输入1(缺少冒号)
{
"A"1
}
//输入2(方括号不完整)
{
"A":[12
}
//输入3(花括号不完整)
{
"A": {
"B": 1
}
//输入4(String类型的Object缺少引号)
{
"A": {
"B": tall
}
}
//输入5(多一个逗号)
{
"A": 1,
,
"B": 5
}
//输入6(数组中间多一个逗号)
{
"A":[1,,2]
}
//输入7(数组结尾多一个逗号)
{
"A":[1,2,]
}
//输入8(key缺少引号)
{
A:1
}
//输出
JSON数据不合法!