字符串遍历实现的Json原始解析(Objective-C)
1. 前言
最近在学习Objective-C(以下简称objc),要做一个Json解析器来巩固一下知识,顺便练练手感。objc的Cocoa库中有一个NSJSONSerialization工具类,用该类的api可以很容易地进行json解析,据说效率还很高。不过,这里我不是用这个类来实现的,而是使用了最原始的字符串遍历的方式来自己进行解析。由于没有很严谨的测试,难免会很容易崩溃或者判断错误之类的,请多海涵。
2. 动手前须知
本次项目的输入是Json字符串(NSString),输出是解析结果(NSDictionary,NSArray或NSString)。如果json格式不正确,会输出Json格式不合法,请检查后重试。
objc中,字符串中读取字符要怎么读?如果只是单纯地使用charAtIndex的话,不同编码格式来读中文时可能会有差错,而且返回的是unichar,不能用来和NSString比较。因此我采用取长度为1的子串来遍历字符串,即类似于这样[inputString substringWithRange: NSMakeRange(index, 1)]
,这种方式就能准确地取一个字符。
该次实现使用的是递归生成解析对象,即在解析方法的实现中调用自己。主要原因是json中一个对象的value可能是一个对象,这种形式最适合用递归来实现。
3. 流程图
4. 实现
- 去除特殊符号
输入的json字符串中可能会存在回车符\n
,换行符\r
或制表符\t
等,所以我们优先去除这些特殊字符
inputJson = [inputJson stringByReplacingOccurrencesOfString: @"\n" withString: @""]; //去除回车
inputJson = [inputJson stringByReplacingOccurrencesOfString: @"\r" withString: @""]; //去除换行
inputJson = [inputJson stringByReplacingOccurrencesOfString: @"\t" withString: @""]; //去除tab
- 遍历字符串
从这里就算是正式开始解析了,由于本人没有想到要怎么先进行格式验证再进行解析,我选择边解析边验证,解析到不符合json的格式的地方就结束解析。
先从第一个非空格字符开始判断,分为几种可能,{ -> 对象、[ -> 数组、false 、true、null或 " -> 字符串
。需要一一进行判断。
– {
我这里先去掉首位的{}
再进行接下来的解析
//先去掉首尾的{},从后往前找,必须先遇到}否则格式错误
for (j = length-1; j > 0; j--) {
rightChar = [inputJson substringWithRange: NSMakeRange(j, 1)];
if ([rightChar isEqualToString: @" "]) {
continue;
}
if (![rightChar isEqualToString: @"}"]) {
return nil;
} else {
break;
}
}
inputJson = [inputJson substringWithRange: NSMakeRange( i+1, j-i-1)];
length = [inputJson length];
i = 0;
然后一个对象可以为空或者必定有一个或多个"key":value
(key是任意字符串,value可以是对象、数组、数字、字符串等)这种格式的字符串,否则格式非法。在继续深入遍历之前,我们先定义几个用到的flag
// rightQuote表示key的右引号,leftQuote表示key的左引号,hasColon表示key-value之间的冒号
// firstKeyValue表示是否在找第一对key-value,metComma表示是否已经遇到逗号,foundValue表示找到key之后是否找到对应的value
BOOL rightQuote = NO, leftQuote = NO, hasColon = NO, firstKeyValue = YES, metComma = YES, foundValue = NO;
然后我们深入遍历寻找key
//跳过空格
if ([leftChar isEqualToString: @" "]) {
continue;
}
//还没遇到左引号之前不能遇到其他字符
if ( (![leftChar isEqualToString: @"\""]) && leftQuote == NO ) {
//除非是第一个键值对之后的key,其之前可以遇到逗号
if (!firstKeyValue && [leftChar isEqualToString: @","] && metComma == NO) {
metComma = YES;
continue;
}
else {
return nil;
}
}
//寻到key,非第一对键值对的引号之前必须遇到逗号,已经找到key的话就不需要执行该if语句的方法块
if ([leftChar isEqualToString: @"\""] &&