Gson同字段不同類型數據解析總結

前言

       最近使用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<CanLoanBookBean> 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<List<CanLoanBookBean>>() {}.getType();
				// 把JsonElement對象轉換成JsonArray
				List<CanLoanBookBean> 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<Object> 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 <T> List<T> performTransform(Object filedObjValue, Class filedMapClass) {
        List<T> beanList = new ArrayList<T>();
        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<List<T>>() {}.getType();
//            // 把JsonElement對象轉換成JsonArray
//            List<T> list = gson.fromJson(element, type);


            List<T> 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 <T>
     * @return
     */
    public static <T> List<T> jsonToList(String json, Class clazz) {
        Type type = new ParameterizedTypeImpl(clazz);
        List<T> list = new Gson().fromJson(json, type);
        return list;
    }

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

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

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

        /**
         * 返回實際類型組成的數據,比如Map<String,Long> map的類型是:java.lang.String、java.lang.Long
         * @return
         */
        @Override
        public Type[] getActualTypeArguments() {
            return new Type[] { clazz };
        }

        /**
         * 返回原生類型,比如Map<String,Long> map的原生類型爲java.util.Map
         * @return
         */
        @Override
        public Type getRawType() {
            return List.class;
        }

        /**
         * 返回 Type 對象,表示此類型是其成員之一的類型,比如Map.Entry<Long,Short> 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<CanLoanBookBean>  canLoanBookBeans=  (List<CanLoanBookBean>) bookBean.getCanLoanBook();
		System.out.println("canLoanBookBeans:" + canLoanBookBeans);

在Retrofit使用示例:

  RetrofitUtils.getInstance().getService().getBookDetail(v_recno, v_curtable, time)

                .map((Function<BookDetail, BookDetail>) bookDetail -> {
            
                    JsonUtils.performTransformWithEvaluation(bookDetail,"setCanLoanBook",bookDetail.getCanLoanBook(),BookDetail.CanLoanBookBean.class);
                    return bookDetail;
                })
                .compose(RxSchedulers.<BookDetail>applySchedulers())
                .subscribe((Consumer<BookDetail>) bookDetail -> {


                }, (Consumer<Throwable>) throwable -> {

                });

 上面還可以再優化一下,自定義一個Gson反序列化器,把上述方法放進序列化器中,這樣網絡請求後,不需要對數據進行map轉換。

方案對比

方案1:是最省事的,不建議後端數據接口同字段返回不同類型數據(適合字段數據類型固定,正常情況都是這種數據)

方案2:解析費時費力,完全手動解析(適合大量字段數據類型變化的情況)

方案3(建議):不用改Retrofit現有Gson轉換器,只需要對特殊字段處理即可(適合少量字段數據類型變化的情況,如果都變化,這樣的json數據存在意義不大)

總結 

       本次就是開發過程中遇到的問題,只要網絡請求出現類似的問題:同字段返回不同類型數據,解析框架出現IllegalStateException,都可以用上述方案3解決。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章