Android扫雷:JSON 解析java.lang.IllegalStateException: Expected a name but was NUMBER 原因及解决方案

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":["[email protected]"]}
     ]
}

整个是一个大对象,然后里面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_OBJECTEND_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()结尾,不过这种情况应该不多见.

只要小心点,这些情况都是很容易避免的.实在不行,就使用大法吧.或者官方文档.

发布了45 篇原创文章 · 获赞 4 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/ZM_Yang/article/details/70135839