JsonReader:java.lang.IllegalStateException: Expected a name but was NUMBER
JSON (JavaScript Object Notation),是一种轻量级的数据交换格式,不仅易于程序生成和解析,人也很容易看懂,并且不依赖于具体的语言,所以适用范围很广.
用Java解析JSON
格式文档,有很多现成的库,比如Gson
或者Jackson
等,也可以直接使用Android提供的JsonReader
这个类,其实都可以,只不过有些比较简便.原理都差不多.
由于JSON是可以嵌套的,对象里面的值可以数字,字符串,浮点读书,也可以是另一对象,或者是数组.而这些对象或者数组中,又可以继续套着数组或者对象……一层套一层.这就使得解析的时候,一不小心就会出错.最常见的就是IllegelStateException
.例如:
{
"name": "zmychou",
"age": 21,
"Addr": "Beijing",
"contact": [
{"type":"cellphone","detail":["12345678900",10987654321]},
{"type":"mail","detail":["zmychou@zmychou.com"]}
]
}
整个是一个大对象,然后里面contact
的值是一个数组,数组又可以包含很多个对象……一直这样下去.而根据JSON 文件解析的参考文档RFC 4627的标注,解析JSON文档是按照起止分隔符进行,并且是深度优先.说的简单点,就是从文档其实部分开始,依次寻找一些特定的分隔符,比如右花括号{
就是对象的开始,而右花括号}
就是对象的结束,中括号也是一样.只要稍有不慎,就会使得这严格相对的起止出现差错,就会抛出IllegelStateException
异常.
IllegalStateException
主要有两种情况:
1. Expected a name but was NUMBER
,其中的NUMBER
也可换成STRING
或者BOOLEAN
等;
2. Expected a END_OBJECT but was END_ARRAY
,其中的END_OBJECT
和END_ARRAY
也可以替换成BEGIN_OBJECT
等.
造成1的原因很可能是使用nextName()
时,没有使用next*()族
(其中*可以使Int
,Long
等)或者skipValue()
进行配套使用.前面说过,JSON解析是深度优先的,而JSON的结构是键值对的形式存在,如果只使用了nextName()
忽略了next*()族
或者skipValue()
进行值的解析,那么,下次在使用nextName()
的时候,其实定位到的是前一个键的值上面,就会抛出异常.例如,解析上面的例子:
public ArrayList<String> parser(Context context) {
try {
File file = openFile(context);
JsonReader jr = new JsonReader(new InputStreamReader(new FileInputStream(file), "UTF-8"));
jr.beginObject();
Log.e("all!",jr.toString());
JsonToken token = jr.peek();
while (jr.hasNext()) {
switch (jr.nextName()) {
case "name":
Log.e("timestamp", jr.nextString());
break;
case "age":
//如果我们注释掉下面这句
//Log.e("finish", jr.nextInt() + "");
break;
case "Addr":
Log.e("start", jr.nextString());
break;
default:
//或者我们缺少下面这句,就会出现异常
jr.skipValue();
break;
}
}
jr.endObject();
jr.close();
} catch (FileNotFoundException e) {
//TO-DO save log or tell user
} catch (IOException e) {
//TO-DO save log or tell user
}
}
上例中,如果最后的default语句没有处理跳过,或者任意一个没有使用配套解析值的next*
语句,就会抛出异常.比如,在nextName()
的结果是age
,但是现在我们不想解析出年龄了,我们就没管,break开始新一轮循环,那么此时nextName()
定位到的就是age
的值,也就是21,显然,这时的Token是NUMBER
,而我们给的却是NAME
,异常妥妥的.因此,如果使用了nextName()
,在遇到不是我们希望解析的键值对时,用skipValue()
跳过,使得状态正常.
对于第二种情况,就是没能正确找到对应对象的结束,很有可能是我们使用了beginObject()
之后,忘记了配套的使用endObject()
,beginArray()对
也是类似的.也可能是我们陪错对,例如用beginObject()
开头却使用了endArray()
结尾,不过这种情况应该不多见.