在前后端的數(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)Adapter的read和write方法
由于代碼太長,這里只給出修改部分的代碼
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也是可以的。由于instanceCreators在GsonBuilder中是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é)可以看
記錄,分享,交流。