Gson同字段不同类型数据解析总结

前言

       最近使用Retrofit进行网络请求时,自带Gson解析json时遇到一个问题,返回的json数据中某个字段可能为jsonArray,也可能是jsonObject,也有可能为空(即同一个字段,返回可能是对象,数组,null)。

错误回顾

测试数据:

Gson同字段不同类型数据解析总结_第1张图片

如果返回的数据类型有两种以上,但你定义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 listObjects = performTransform(filedObjValue, filedMapClass);
        Class clazz = sourceObj.getClass();
        Method method = null;
        try {
            method = clazz.getMethod(methodName, Object.class);
            method.invoke(sourceObj, listObjects);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 统一转换成List输出,对于基本类型,取出集合中的值即可
     *
     * @param filedObjValue 需要处理的字段对象值
     * @param filedMapClass 处理的字段对象映射Class
     * @return
     */
    public static  List performTransform(Object filedObjValue, Class filedMapClass) {
        List beanList = new ArrayList();
        Gson gson = new Gson();
        String jsonStr = gson.toJson(filedObjValue);
        JsonParser parser = new JsonParser();
        JsonElement element = parser.parse(jsonStr);
        if (element.isJsonObject()) {
            // 把JsonElement对象转换成JsonObject
            T t = (T) gson.fromJson(element, filedMapClass);
            beanList.add(t);
        } else if (element.isJsonArray()) {
            //下面会导致T为LinkedTreeMap,说明Gson解析时不支持泛型
//            Type type = new TypeToken>() {}.getType();
//            // 把JsonElement对象转换成JsonArray
//            List list = gson.fromJson(element, type);


            List list = jsonToList(element,filedMapClass);
            beanList.addAll(list);
        } else if (element.isJsonPrimitive()) {
            T t = (T) gson.fromJson(element, filedMapClass);
            beanList.add(t);
        } else {
            // element.isJsonNull()
            return null;
        }
        return beanList;
    }

    /**
     * 通过json字符串转List
     * @param json
     * @param clazz
     * @param 
     * @return
     */
    public static  List jsonToList(String json, Class clazz) {
        Type type = new ParameterizedTypeImpl(clazz);
        List list = new Gson().fromJson(json, type);
        return list;
    }

    /**
     * 通过element转List
     * @param element
     * @param clazz
     * @param 
     * @return
     */
    public static  List jsonToList(JsonElement element, Class clazz) {
        Type type = new ParameterizedTypeImpl(clazz);
        List list = new Gson().fromJson(element, type);
        return list;
    }

   /**
     * 自定义ParameterizedType
     */
    private static class ParameterizedTypeImpl implements ParameterizedType {
        Class clazz;

        public ParameterizedTypeImpl(Class clz) {
            clazz = clz;
        }

        /**
         * 返回实际类型组成的数据,比如Map map的类型是:java.lang.String、java.lang.Long
         * @return
         */
        @Override
        public Type[] getActualTypeArguments() {
            return new Type[] { clazz };
        }

        /**
         * 返回原生类型,比如Map map的原生类型为java.util.Map
         * @return
         */
        @Override
        public Type getRawType() {
            return List.class;
        }

        /**
         * 返回 Type 对象,表示此类型是其成员之一的类型,比如Map.Entry map的OwnerType为java.util.Map
         * @return
         */
        @Override
        public Type getOwnerType() {
            return null;
        }
    }
 
  

关于参数化类型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解决。

你可能感兴趣的:(Gson同字段不同类型数据解析总结)