Gson異常數(shù)據(jù)解析之通過源碼尋找解決方法(一)

在前后端的數(shù)據(jù)交互中,JSON是最常用的數(shù)據(jù)交換格式,但是在交互的過程中總會有一些奇葩的數(shù)據(jù)出現(xiàn),對于后端來說影響可能還不是很大,但是對于Android端來說,那就是暴擊呀(程序已停止運行),這 除了懟死寫這些數(shù)據(jù)的同事 就需要我們想辦法去解決。在Android開發(fā)中我們使用的最多的JSON解析庫就是Gson,當(dāng)然在后端中可能也會有人使用,所以本篇學(xué)習(xí)的是使用Gson庫(Gson2.8.5)時如何處理奇葩的數(shù)據(jù)。

本文需要對Gson有一定的了解才能看得懂,如果對Gson還不是很了解的同學(xué)推薦去看看怪盜kidou大佬寫的Gson系列:你真的會用Gson嗎?Gson使用指南。本文只是提供Gson解析異常數(shù)據(jù)的思路,實驗所使用的Json數(shù)據(jù)只是舉例說明,在實際應(yīng)用中還是得根據(jù)具體的業(yè)務(wù)需求來對數(shù)據(jù)進(jìn)行處理。

本篇的主要內(nèi)容

  • String 類型數(shù)據(jù)的處理
  • Number(Integer,Float,Double,Long…) 類型數(shù)據(jù)的處理
  • Collection(List,Set,Map…) 類型數(shù)據(jù)的處理

由于實驗需要,我們先準(zhǔn)備以下幾個實體類:

Result.java

public class Result<T> {
        private Integer code;
        private String msg;
        private T data;

        //getters,setters,toString
}

Province.java

public class Province {
        private Integer id;
        private String name;
        private List<City> cities;

        //getters,setters,toString
}

City.java

public class City {
        private Integer id;
        private String name;

        //getters,setters,toString
}

一、最常見之 String null 轉(zhuǎn)為 空字符串

現(xiàn)在我們通過接口請求到了一段JSON數(shù)據(jù),需要顯示其中的name

 {
        "code":200,
        "msg":"success",
        "data":{
            "id":1,
            "name":null
        }
}

如果直接TextView.setText(City.getName()),那程序肯定直接掛了,所以得先進(jìn)行非空判斷,但是如果每個取值的地方都寫個非空判斷,那復(fù)制粘貼都得寫到手殘呀。我們作為具有高智商的程序員,肯定得想出解決偷懶的辦法在某個地方統(tǒng)一處理掉null的數(shù)據(jù)。因為我們是使用Gson進(jìn)行解析,所以我們先去看看源碼中是怎么處理String類型的數(shù)據(jù)的。

在源碼中我們找到了一個可疑的類com.google.gson.internal.bind.TypeAdapters,發(fā)現(xiàn)里面有很多TypeAdapter,在里面我們找到了關(guān)于String類型的處理

可以看到在反序列化時,當(dāng)peek == JsonToken.NULL會返回null,所以我們可以在這上面通過自定義對序列化以及反序列化做處理,看看效果如何。

1、創(chuàng)建一個StringAdapter.java

    public class StringAdapter extends TypeAdapter<String> {

        @Override
        public void write(JsonWriter out, String value) throws IOException {
            if (value == null) {
                out.value("");
                return;
            }
            out.value(value);
        }

        public String read(JsonReader reader) throws IOException {
            if (reader.peek() == JsonToken.NULL) {
                reader.nextNull();
                return "";
            }
            return reader.nextString();
        }
    }

2.使用方法

    Gson gson = new GsonBuilder().serializeNulls()
                    .registerTypeAdapter(String.class, new StringAdapter())
                    .create();
    //序列化
    Result<City> result = new Result<>();
    String resultJson = gson.toJson(new Result<>());
    Log.e("序列化結(jié)果:", resultJson);
    //反序列化
    String jsonStr = "{\"code\":200,\"msg\":\"success\",\"data\":{\"id\":1,\"name\":null}}";
    Result<City> result = gson.fromJson(jsonStr, new TypeToken<Result<City>>() {}.getType());
    Log.e("反序列化結(jié)果:", result.toString());

3.測試結(jié)果

    序列化結(jié)果:
    {"code":null,"data":null,"msg":""}

    反序列化結(jié)果:
    Result{code=200, msg='success', data=City{id=1, name=''}}

可以看到name由null轉(zhuǎn)為了空字符串,實驗成功!

二、特殊需求之Number類型的處理

現(xiàn)在我們通過接口請求到了一段JSON數(shù)據(jù),需要取code出來判斷

    {
        "code":"",
        "msg":"success",
        "data":{
            "id":1,
            "name":"Beijing"
        }
    }

如果解析肯定會拋出com.google.gson.JsonSyntaxException: java.lang.NumberFormatException: empty String。通過上面的學(xué)習(xí),同學(xué)們看到這里肯定已經(jīng)想到了通過參考源碼中Integer類型的處理來自定義解析。

1、創(chuàng)建一個IntegerAdapter.java

public class IntegerAdapter extends TypeAdapter<Number> {
    @Override
    public void write(JsonWriter out, Number value) throws IOException {
        if (value == null) {
            out.value(0);
            return;
        }
        out.value(value);
    }

    public Number read(JsonReader reader) throws IOException {
        if (reader.peek() == JsonToken.NULL) {
            reader.skipValue();
            return 0;
        } else if (reader.peek() == JsonToken.STRING) {
            try {
                return Integer.valueOf(reader.nextString());
            } catch (NumberFormatException e) {
                e.printStackTrace();
                return 0;
            } catch (IOException e) {
                e.printStackTrace();
                return 0;
            }
        }

        try {
            return reader.nextInt();
        } catch (NumberFormatException e) {
            throw new JsonSyntaxException(e);
        }
    }
}

2.使用方法

    Gson gson = new GsonBuilder().serializeNulls()
                    .registerTypeAdapterFactory(TypeAdapters.newFactory(int.class, Integer.class, new IntegerAdapter()))
                    .create();
    //序列化
    Result<City> result = new Result<>();
    String resultJson = gson.toJson(new Result<>());
    Log.e("序列化結(jié)果", resultJson);
    //反序列化
    String jsonStr = "{\"code\":\"\",\"msg\":\"success\",\"data\":{\"id\":1,\"name\":\"Beijing\"}}";
    Result<City> result = gson.fromJson(jsonStr, new TypeToken<Result<City>>() {}.getType());
    Log.e("反序列化結(jié)果", result.toString());

3.測試結(jié)果

    序列化結(jié)果:
    {"code":0,"data":null,"msg":""}

    反序列化結(jié)果:
    Result{code=0, msg='success', data=City{id=1, name='Beijing'}}

可以看到code由空字符串轉(zhuǎn)為了0,實驗成功!

Float,Double,Long等Number類型可參考以上做法。

三、Collection 為 null的處理

現(xiàn)在我們通過接口請求到了一段JSON數(shù)據(jù),需要獲得省市列表

    {
        "code":200,
        "msg":"success",
        "data":[
            {
                "id":1,
                "name":"Beijing",
                "cities":null
            },
            {
                "id":2,
                "name":"Guangdong",
                "cities":[
                    {
                        "id":1,
                        "name":"Guangzhou"
                    },
                    {
                        "id":2,
                        "name":"Shenzhen"
                    }
                ]
            }
        ]
    }

我們在做省市聯(lián)動選擇列表時,一般對于直轄市的城市列表都是傳一個空列表,但是很多時候后端傳回來的是一個null,因為直轄市下查不到城市,這時候就需要做判斷Province.getCities()!=null。此處只是列舉省市列表場景,實際中可能還有其他的場景,需要對每個List的調(diào)用都進(jìn)行非空判斷。這時候我們就需要通過Gson直接把null List轉(zhuǎn)為 empty List。

有了上面的學(xué)習(xí),我們很熟練的就去Gson源碼里面查看,此處捕獲一只野生的com.google.gson.internal.bind.CollectionTypeAdapterFactory

我們使用CV大法熟練的復(fù)制一份出來

    public final class CollectionTypeAdapterFactory implements TypeAdapterFactory {
      private final ConstructorConstructor constructorConstructor;

      public CollectionTypeAdapterFactory(ConstructorConstructor constructorConstructor) {
        this.constructorConstructor = constructorConstructor;
      }

      @Override
      public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
        Type type = typeToken.getType();

        Class<? super T> rawType = typeToken.getRawType();
        if (!Collection.class.isAssignableFrom(rawType)) {
          return null;
        }

        Type elementType = $Gson$Types.getCollectionElementType(type, rawType);
        TypeAdapter<?> elementTypeAdapter = gson.getAdapter(TypeToken.get(elementType));
        ObjectConstructor<T> constructor = constructorConstructor.get(typeToken);

        @SuppressWarnings({"unchecked", "rawtypes"}) // create() doesn't define a type parameter
        TypeAdapter<T> result = new Adapter(gson, elementType, elementTypeAdapter, constructor);
        return result;
      }

      private static final class Adapter<E> extends TypeAdapter<Collection<E>> {
        private final TypeAdapter<E> elementTypeAdapter;
        private final ObjectConstructor<? extends Collection<E>> constructor;

        public Adapter(Gson context, Type elementType,
            TypeAdapter<E> elementTypeAdapter,
            ObjectConstructor<? extends Collection<E>> constructor) {
          this.elementTypeAdapter =
              new TypeAdapterRuntimeTypeWrapper<E>(context, elementTypeAdapter, elementType);
          this.constructor = constructor;
        }

        @Override public Collection<E> read(JsonReader in) throws IOException {
          if (in.peek() == JsonToken.NULL) {
            in.nextNull();
            return null;
          }

          Collection<E> collection = constructor.construct();
          in.beginArray();
          while (in.hasNext()) {
            E instance = elementTypeAdapter.read(in);
            collection.add(instance);
          }
          in.endArray();
          return collection;
        }

        @Override public void write(JsonWriter out, Collection<E> collection) throws IOException {
          if (collection == null) {
            out.nullValue();
            return;
          }

          out.beginArray();
          for (E element : collection) {
            elementTypeAdapter.write(out, element);
          }
          out.endArray();
        }
      }
    }

但是發(fā)現(xiàn)了一個錯誤


趕緊到源碼中查看com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper,發(fā)現(xiàn)居然是使用默認(rèn)訪問修飾符(Java中不寫訪問修飾符時默認(rèn)的訪問修飾符是default,對于同一個包中的其他類相當(dāng)于公開的(public),對于不是同一個包中的其他類相當(dāng)于私有的(private))。

所以我們是不能直接使用了,只好再次施展CV大法(避免注釋占用行數(shù),下面代碼已經(jīng)把源碼中的注釋去掉)。

    public class TypeAdapterRuntimeTypeWrapper<T> extends TypeAdapter<T> {
        private final Gson context;
        private final TypeAdapter<T> delegate;
        private final Type type;

        TypeAdapterRuntimeTypeWrapper(Gson context, TypeAdapter<T> delegate, Type type) {
            this.context = context;
            this.delegate = delegate;
            this.type = type;
        }

        @Override
        public T read(JsonReader in) throws IOException {
            return delegate.read(in);
        }

        @SuppressWarnings({"rawtypes", "unchecked"})
        @Override
        public void write(JsonWriter out, T value) throws IOException {
            TypeAdapter chosen = delegate;
            Type runtimeType = getRuntimeTypeIfMoreSpecific(type, value);
            if (runtimeType != type) {
                TypeAdapter runtimeTypeAdapter = context.getAdapter(TypeToken.get(runtimeType));
                if (!(runtimeTypeAdapter instanceof ReflectiveTypeAdapterFactory.Adapter)) {      
                    chosen = runtimeTypeAdapter;
                } else if (!(delegate instanceof ReflectiveTypeAdapterFactory.Adapter)) {
                    chosen = delegate;
                } else {
                    chosen = runtimeTypeAdapter;
                }
            }
            chosen.write(out, value);
        }

        private Type getRuntimeTypeIfMoreSpecific(Type type, Object value) {
            if (value != null
                    && (type == Object.class || type instanceof TypeVariable<?> || type instanceof Class<?>)) {
                type = value.getClass();
            }
            return type;
        }
    }

1、修改CollectionTypeAdapterFactory內(nèi)Adapterreadwrite方法

由于代碼太長,這里只給出修改部分的代碼

    public Collection<E> read(JsonReader in) throws IOException {
      if (in.peek() == JsonToken.NULL) {
          in.nextNull();
          //源碼中是 return null,改為 return empty constructor
          return constructor.construct();
      }
      //more than
    }

    public void write(JsonWriter out, Collection<E> collection) throws IOException {
    if (collection == null) {
        //源碼中是 out.nullValue(),改為 []
        out.beginArray();
        out.endArray();
        return;
    }

2.使用方法

        Gson gson = new GsonBuilder().serializeNulls()
                        .registerTypeAdapterFactory(new CollectionTypeAdapterFactory(new ConstructorConstructor(new HashMap<Type, InstanceCreator<?>>())))
                        .create();
        //序列化
        List<Province> provinces = new ArrayList<>();
        Province province = new Province();
        province.setId(1);
        province.setName("Beijing");
        provinces.add(province);

        Result<List<Province>> result = new Result<>();
        result.setData(provinces);

        String resultJson = gson.toJson(result);
        Log.e("序列化結(jié)果", resultJson);
        //反序列化
        String jsonStr = "{\"code\":200,\"msg\":\"success\",\"data\":[{\"id\":1,\"name\":\"Beijing\",\"cities\":null},{\"id\":2,\"name\":\"Guangdong\",\"cities\":[{\"id\":1,\"name\":\"Guangzhou\"},{\"id\":2,\"name\":\"Shenzhen\"}]}]}";
        Result<City> result = gson.fromJson(jsonStr, new TypeToken<Result<City>>() {}.getType());
        Log.e("反序列化結(jié)果", result.toString());

對于register的時候有一個情況,在源碼中是使用GsonBuilder中的Map<Type, InstanceCreator<?>> instanceCreators,即new CollectionTypeAdapterFactory(new ConstructorConstructor(instanceCreators)),我們來看一下這個instanceCreators是個啥玩意

可以看到它是當(dāng)register了InstanceCreator類型的TypeAdapter時才起作用,否則直接使用new HashMap也是可以的。由于instanceCreatorsGsonBuilder中是private的,所以我們只能通過反射來獲取

    GsonBuilder gsonBuilder = new GsonBuilder();
    try {
        Class builder = gsonBuilder.getClass();
        Field f = builder.getDeclaredField("instanceCreators");
        f.setAccessible(true);
        Map<Type, InstanceCreator<?>> val = (Map<Type, InstanceCreator<?>>) f.get(gsonBuilder);
        builder.registerTypeAdapterFactory(new CollectionTypeAdapterFactory(new ConstructorConstructor(val)));
    } catch (NoSuchFieldException | IllegalAccessException e) {
        e.printStackTrace();
        builder.registerTypeAdapterFactory(new CollectionTypeAdapterFactory(new ConstructorConstructor(new HashMap<Type, InstanceCreator<?>>())));
    }

3.測試結(jié)果

序列化結(jié)果:
{"code":null,"data":[{"cities":[],"id":1,"name":"Beijing"},{"cities":[],"id":2,"name":"Beijing"}],"msg":null}

 反序列化結(jié)果:
Result{code=200, msg='success', data=[Province{id=1, name='Beijing', cities=[]}, Province{id=2, name='Guangdong', cities=[City{id=1, name='Guangzhou'}, City{id=2, name='Shenzhen'}]}]}

可以看到cities由null轉(zhuǎn)為了[],實驗成功!

為了避免篇幅過長,所以將文章拆分成了兩篇,想繼續(xù)學(xué)習(xí)研究的同學(xué)可以看

記錄,分享,交流。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容