在前后端的数据交互中,JSON是最常用的数据交换格式,但是在交互的过程中总会有一些奇葩的数据出现,对于后端来说影响可能还不是很大,但是对于Android端来说,那就是暴击呀(程序已停止运行),这 除了怼死写这些数据的同事 就需要我们想办法去解决。在Android开发中我们使用的最多的JSON解析库就是Gson,当然在后端中可能也会有人使用,所以本篇学习的是使用Gson库(Gson2.8.5)时如何处理奇葩的数据。
本文需要对Gson有一定的了解才能看得懂,如果对Gson还不是很了解的同学推荐去看看怪盗kidou大佬写的Gson系列:你真的会用Gson吗?Gson使用指南。本文只是提供Gson解析异常数据的思路,实验所使用的Json数据只是举例说明,在实际应用中还是得根据具体的业务需求来对数据进行处理。
本篇的主要内容
-
String
类型数据的处理 -
Number(Integer,Float,Double,Long…)
类型数据的处理 -
Collection(List,Set,Map…)
类型数据的处理
由于实验需要,我们先准备以下几个实体类:
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 转为 空字符串
现在我们通过接口请求到了一段JSON数据,需要显示其中的name
{
"code":200,
"msg":"success",
"data":{
"id":1,
"name":null
}
}
如果直接TextView.setText(City.getName())
,那程序肯定直接挂了,所以得先进行非空判断,但是如果每个取值的地方都写个非空判断,那复制粘贴都得写到手残呀。我们作为具有高智商的程序员,肯定得想出解决偷懒的办法在某个地方统一处理掉null的数据。因为我们是使用Gson进行解析,所以我们先去看看源码中是怎么处理String类型的数据的。
在源码中我们找到了一个可疑的类com.google.gson.internal.bind.TypeAdapters
,发现里面有很多TypeAdapter,在里面我们找到了关于String
类型的处理
可以看到在反序列化时,当peek == JsonToken.NULL
会返回null
,所以我们可以在这上面通过自定义对序列化以及反序列化做处理,看看效果如何。
1、创建一个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("序列化结果:", 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("反序列化结果:", result.toString());
3.测试结果
序列化结果:
{"code":null,"data":null,"msg":""}
反序列化结果:
Result{code=200, msg='success', data=City{id=1, name=''}}
可以看到name由null转为了空字符串,实验成功!
二、特殊需求之Number类型的处理
现在我们通过接口请求到了一段JSON数据,需要取code出来判断
{
"code":"",
"msg":"success",
"data":{
"id":1,
"name":"Beijing"
}
}
如果解析肯定会抛出com.google.gson.JsonSyntaxException: java.lang.NumberFormatException: empty String
。通过上面的学习,同学们看到这里肯定已经想到了通过参考源码中Integer
类型的处理来自定义解析。
1、创建一个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("序列化结果", resultJson);
//反序列化
String jsonStr = "{\"code\":\"\",\"msg\":\"success\",\"data\":{\"id\":1,\"name\":\"Beijing\"}}";
Result<City> result = gson.fromJson(jsonStr, new TypeToken<Result<City>>() {}.getType());
Log.e("反序列化结果", result.toString());
3.测试结果
序列化结果:
{"code":0,"data":null,"msg":""}
反序列化结果:
Result{code=0, msg='success', data=City{id=1, name='Beijing'}}
可以看到code由空字符串转为了0,实验成功!
Float,Double,Long等Number类型可参考以上做法。
三、Collection 为 null的处理
现在我们通过接口请求到了一段JSON数据,需要获得省市列表
{
"code":200,
"msg":"success",
"data":[
{
"id":1,
"name":"Beijing",
"cities":null
},
{
"id":2,
"name":"Guangdong",
"cities":[
{
"id":1,
"name":"Guangzhou"
},
{
"id":2,
"name":"Shenzhen"
}
]
}
]
}
我们在做省市联动选择列表时,一般对于直辖市的城市列表都是传一个空列表,但是很多时候后端传回来的是一个null,因为直辖市下查不到城市,这时候就需要做判断Province.getCities()!=null
。此处只是列举省市列表场景,实际中可能还有其他的场景,需要对每个List
的调用都进行非空判断。这时候我们就需要通过Gson直接把null List
转为 empty List
。
有了上面的学习,我们很熟练的就去Gson源码里面查看,此处捕获一只野生的com.google.gson.internal.bind.CollectionTypeAdapterFactory
我们使用CV大法熟练的复制一份出来
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();
}
}
}
但是发现了一个错误
赶紧到源码中查看com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper
,发现居然是使用默认访问修饰符(Java中不写访问修饰符时默认的访问修饰符是default,对于同一个包中的其他类相当于公开的(public),对于不是同一个包中的其他类相当于私有的(private))。
所以我们是不能直接使用了,只好再次施展CV大法(避免注释占用行数,下面代码已经把源码中的注释去掉)。
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
内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("序列化结果", 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("反序列化结果", result.toString());
对于register的时候有一个情况,在源码中是使用GsonBuilder
中的Map<Type, InstanceCreator<?>> instanceCreators
,即new CollectionTypeAdapterFactory(new ConstructorConstructor(instanceCreators))
,我们来看一下这个instanceCreators
是个啥玩意
可以看到它是当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.测试结果
序列化结果:
{"code":null,"data":[{"cities":[],"id":1,"name":"Beijing"},{"cities":[],"id":2,"name":"Beijing"}],"msg":null}
反序列化结果:
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转为了[],实验成功!
为了避免篇幅过长,所以将文章拆分成了两篇,想继续学习研究的同学可以看
记录,分享,交流。
网友评论