最近使用Retrofit进行网络请求时,自带Gson解析json时遇到一个问题,返回的json数据中某个字段可能为jsonArray,也可能是jsonObject,也有可能为空(即同一个字段,返回可能是对象,数组,null)。
测试数据:
如果返回的数据类型有两种以上,但你定义json的实体类bean属性类型时,可能只使用了jsonObject或者jsonArray,网络请求时会报如下错误:
Caused by: java.lang.IllegalStateException: Expected BEGIN_ARRAY but was BEGIN_OBJECT at line 4 column 514 path $.CanLoanBook
at com.google.gson.stream.JsonReader.beginArray(JsonReader.java:350)
at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.read(CollectionTypeAdapterFactory.java:80)
at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.read(CollectionTypeAdapterFactory.java:61)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read(ReflectiveTypeAdapterFactory.java:129)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:22
上面出错原因是字段CanLoanBook使用了数组进行映射,但CanLoanBook实际是对象。根据上面错误提示可能你会这样做:
把实体类的字段从原来的
private List CanLoanBook;
改为如下:
private CanLoanBookBean CanLoanBook;
上面确实临时 解决了问题,但当后端CanLoanBook返回是数组时,上面还是会报错,不妨自己试一下。
另外,网上也有一些方案通过自定义Gson序列化器或者解析异常时,使用另一种是实体类处理,这两种方式要么处理麻烦,要么产生多余的类。
1、与后端协商,规范数据格式,保证返回字段类型不变
这种情况虽然处理起来最省事,得到期望结果,但是后端的大佬可能不会随便改动数据接口,让你想办法解决。严格来说,这种字段数据类型变化是不规范化的。
2、把数据映射为ResponseBody再解析
这种情况要先把ResponseBody转成字符串,再采用对应框架,逐个字段解析封装到对应实体,然后需要对特殊字段进行数据类型判断。这种方案虽然可取,但解析麻烦,费时,显然不给力。
下面顺便介绍下两种方法怎么对json数据字段类型判断:
1.使用Android自带的JSONTokener解析
try {
JSONObject jsonObject = JSONObject(jsonStr);
String canLoanBook = jsonObject.getString("CanLoanBook");
Object obj = new JSONTokener(canLoanBook).nextValue();
if (obj instanceof JSONArray) {
JSONArray jsonArray = (JSONArray) obj;
for (int k = 0; k < jsonArray.length(); k++) {
JSONObject parameterObject = jsonArray.getJSONObject(k);
//开始逐个字段解析,封装到对应bean,省略
}
} else if (obj instanceof JSONObject) {
JSONObject jObj = (JSONObject) obj;
//开始逐个字段解析,封装到对应bean,省略
}
} catch (JSONException e) {
e.printStackTrace();
}
2.使用Gson框架解析
Gson gson = new Gson();
JsonParser parser = new JsonParser();
JsonElement element = parser.parse(jsonStr);
if (element.isJsonObject()) {//假设最外层是object
// 把JsonElement对象转换成JsonObject
JsonObject JsonObject = element.getAsJsonObject();
JsonElement jsonElement = JsonObject.get("CanLoanBook");
if (jsonElement.isJsonObject()) {
CanLoanBookBean canLoanBookBean = gson.fromJson(jsonElement, CanLoanBookBean.class);
} else if (jsonElement.isJsonArray()) {
Type type = new TypeToken>() {}.getType();
// 把JsonElement对象转换成JsonArray
List list = gson.fromJson(jsonElement, type);
}
}
3、把实体类特殊字段的数据类型改为Object再转换
也就是说不管json字段是对象还是数组,统一使用Object接收数据,这样不会出现异常。
/**
* 1.不管是jsonArray还是jsonObject,统一用Object接收
* 2.使用Gson对CanLoanBook字段统一换成List,反射赋值CanLoanBook
* 3.最后对CanLoanBook强转成对应类型
*/
private Object CanLoanBook;
这种方式需要再用gson对Object对象进行手动解析,可以参考上面方案2的第2种解析方式。
附上转换方法:
/**
* 转换和赋值
*
* @param sourceObj 原数据对象
* @param methodName 需要赋值原数据对象的方法名
* @param filedObjValue 需要处理的字段对象值
* @param filedMapClass 处理的字段对象映射Class
*/
public static void performTransformWithEvaluation(Object sourceObj, String methodName, Object filedObjValue,
Class filedMapClass) {
List
关于参数化类型ParameterizedType(除此之外,还有TypeVariable、GenericArrayType、WildcardType,其父接口Type是Java 编程语言中所有类型的公共高级接口) 的用法这里就不多说,在Android开发中很常见,比如:MVP模式中BaseActivity,经常指定一个泛型P(Presenter),让子类继承BaseActivity并制定具体的Presenter;RecyclerView.Adapter等等。
基本使用示例:
BookBean bookBean = gson.fromJson(jsonStr, BookBean.class);
performTransformWithEvaluation(bookBean, "setCanLoanBook", bookBean.getCanLoanBook(), CanLoanBookBean.class);
List canLoanBookBeans= (List) bookBean.getCanLoanBook();
System.out.println("canLoanBookBeans:" + canLoanBookBeans);
在Retrofit使用示例:
RetrofitUtils.getInstance().getService().getBookDetail(v_recno, v_curtable, time)
.map((Function) bookDetail -> {
JsonUtils.performTransformWithEvaluation(bookDetail,"setCanLoanBook",bookDetail.getCanLoanBook(),BookDetail.CanLoanBookBean.class);
return bookDetail;
})
.compose(RxSchedulers.applySchedulers())
.subscribe((Consumer) bookDetail -> {
}, (Consumer) throwable -> {
});
上面还可以再优化一下,自定义一个Gson反序列化器,把上述方法放进序列化器中,这样网络请求后,不需要对数据进行map转换。
方案1:是最省事的,不建议后端数据接口同字段返回不同类型数据(适合字段数据类型固定,正常情况都是这种数据)
方案2:解析费时费力,完全手动解析(适合大量字段数据类型变化的情况)
方案3(建议):不用改Retrofit现有Gson转换器,只需要对特殊字段处理即可(适合少量字段数据类型变化的情况,如果都变化,这样的json数据存在意义不大)
本次就是开发过程中遇到的问题,只要网络请求出现类似的问题:同字段返回不同类型数据,解析框架出现IllegalStateException,都可以用上述方案3解决。