移动端:
不要传null!不要传null!不要传null!
后台:
放心吧,大兄弟,我怎么会传null给你呢(滑稽脸)
结果在对接口时才发现,曾经的誓言都喂了狗,这些null就像垃圾短信一样,时不时的蹦出来骚扰你,轻则影响UI展示,重则导致应用崩溃。于是你怒气冲冲的跑去质问后台,发生如下场景。。
移动端:
为什么要骗我?说好的永不传null呢?难道我们不是最好的朋友吗(滑稽脸)?
后台:
不能啊,怎么会传null呢,我明明改过了啊,你先别急,大兄弟,我们当然是最好的朋友啦(滑稽脸),我看看哪里出了问题。
---------------------------------------------------------------------------------------------------------十年后
后台:哦哦,我漏掉了一个东西,balabala......,放心吧,大兄弟,不会传null了,再传null,你拿刀来见我!
移动端:
哦(我信你个鬼,你个糟老头子坏得很)。
1、万能的百度啊,赐我一个答案吧!结果查到都是这个答案。
gson.serializeNulls();
可以明确的告诉你,不管用,这是序列化用的方法,而我们是反序列化。
2、他给null就给null吧,大不了我在用的时候判断是否为null就好了。
这个方法,应该是最普遍采用的了,看起来是能解决因为null产生崩溃的问题,但是操作起来费时费力,几乎每次在用到值得地方都要判断一次是否为null,要是那个地方忘记判断了,还是避免不了会出现因为null崩溃的问题。
3、做一个日志采集,把因为null崩溃的日志记录下来,等记录到一定的数量后拿去给上级看,强逼后台严禁null值。
emmm,有理有据,可能会起作用,但是毕竟应用是自己写的,崩溃那么多太影响用户体验,而且应用崩溃,人们能想到的第一个背锅的肯定是开发应用的自己,而且可能上级还会质问为什么移动端不加校验。
4、我自己用JsonObject、JsonArray去解析json,如果碰到null值的,手动给他设置一个默认值。
这个想法不错,在自己能触碰到的根源处解决问题,但还是太啰嗦了,每次都要手动转换json,岂不是浪费了Gson这类神器,不过都想到这个方法了,那么能不能在Gson上做做文章,在统一的地方对null值做转换呢?
5、对Gson动手,添加统一的null值转换
先看看我们什么都不做,Gson默认情况下会对null值做什么处理?
先创建一个数据对象,包含我们常用的数据类型。
public class UserBean {
String name;
String gender;
String email;
String address;
int age;
boolean hasMarry;
float weight;
double height;
public UserBean(String name, String gender, String email, String address, int age, boolean hasMarry, float weight, double height) {
this.name = name;
this.gender = gender;
this.email = email;
this.address = address;
this.age = age;
this.hasMarry = hasMarry;
this.weight = weight;
this.height = height;
}
@Override
public String toString() {
return "name:" + name + ",gender:" + gender + ",email:" + email + ",address:" + address + ",age:" + age + ",hasMarry:" + hasMarry + ",weight:" + weight + ",height:" + height;
}
}
接着再创建一个默认的Gson对象出来,反序列化一个字段全是null的Userbean,再toString打印出来。
private void defaultGson(){
Gson gson = new Gson();
UserBean bean = gson.fromJson("{\"address\":null,\"age\":null,\"email\":null,\"gender\":null,\"hasMarry\":null,\"name\":null,\"weight\":null,\"height\":null}", UserBean.class);
Log.i("kkk","默认的Gson:\n");
Log.i("kkk",bean.toString());
}
01-14 14:25:55.914 12966-12966/wowo.kjt.app4 I/kkk: 默认的Gson:
name:null,gender:null,email:null,address:null,age:0,hasMarry:false,weight:0.0,height:0.0
可以看到除了String类型,其他都有默认值,这是Gson为我们做的吗?其实是Java做的,只要是基本数据类型,不管你赋不赋值,都会有默认值,只有对象才会为null,所以在这Gson除了帮我们自动反序列化,其他啥也没帮我们做。
在开发中,最常用的字段类型就是String了,这么重要的类型可不能为null。
扯了这么多,终于到实践的地步了,首先我要介绍一个Gson中的接口。
/*
* @since 2.1
*/
public interface TypeAdapterFactory {
/**
* Returns a type adapter for {@code type}, or null if this factory doesn't
* support {@code type}.
* 返回{@code type}的类型适配器,如果此工厂没有,则返回null
* 支持{@code type}。
*/
TypeAdapter create(Gson gson, TypeToken type);
}
其实这个接口的注释有很长很长一大段,包含示例和解释,想看的可以去源码里看一下,这里我只挑重点放上。
首先这个接口是在Gson2.1才开始有的,所以Gson版本低于2.1的赶紧升级一下版本,这个接口是Gson的类型适配器工厂,里面的方法作用是”返回对应类型的适配器“,如果我们想在反序列化其他对象类型时做一些操作,可以通过这个适配器来完成。比如把字符串全部转换小写,对列表元素做一些预处理等等,所以,我们是不是可以通过它对类型为String的null值做一些操作呢?来试试!
仿照示例代码编写一个StringAdapterFactory和StringAdapter
public static class StringAdapterFactory implements TypeAdapterFactory {
public TypeAdapter create(Gson gson, TypeToken type) {
Class rawType = (Class) type.getRawType();
//如果对象类型为String,返回自己实现的StringAdapter
if (rawType != String.class) {
return null;
}
return (TypeAdapter) new StringAdapter();
}
}
public static class StringAdapter extends TypeAdapter {
public String read(JsonReader reader) throws IOException {
//如果值为null,返回空字符串
if (reader.peek() == JsonToken.NULL) {
reader.nextNull();
return "";
}
return reader.nextString();
}
//序列化用到的,这里我们实现默认的代码就行
public void write(JsonWriter writer, String value) throws IOException {
writer.value(value);
}
}
好了,把上面的Gson代码改一下,加上我们自己实现的StringAdapterFactory试试,通过GsonBuilder添加,构建Gson。
private void customGson(){
GsonBuilder builder = new GsonBuilder();
Gson gson = builder.registerTypeAdapterFactory(new StringAdapterFactory()).create();
UserBean bean = gson.fromJson("{\"address\":null,\"age\":null,\"email\":null,\"gender\":null,\"hasMarry\":null,\"name\":null,\"weight\":null,\"height\":null}", UserBean.class);
Log.i("kkk","自定义的Gson:\n");
Log.i("kkk",bean.toString());
}
01-14 16:25:13.364 25301-25301/wowo.kjt.app4 I/kkk: 自定义的Gson:
name:,gender:,email:,address:,age:0,hasMarry:false,weight:0.0,height:0.0
哈哈,大功告成!讨厌的null值不见了,取而代之的是我们定义的空字符串```
这下不会再有对null字符串操作导致的异常崩溃了✌️
本以为高高兴兴,解决了后台传null的问题,没想到刚过几天安稳日子,程序又崩了!一查原因,好嘛,魔高一尺道高一丈,我升级,后台也升级,这下不光传null了,类型都传错了,我要int,他却传给我一个string,我要string,他给我传过来一个boolean。于是拿着这个问题又和后台人员亲切交流一番,本着防患于未然,我觉得还是在App里加一个处理比较稳妥,这样,下次再有这种情况,起码不影响App的正常使用,防止崩溃。
怎么做呢?还是通过那个Adapter,除了对null判断,再加上对其他类型的判断,比如对string和int类型的值校验,其他基本类型的校验一样,可以照着这两个写:
public static class StringAdapter extends TypeAdapter {
public String read(JsonReader reader) throws IOException {
if (reader.peek() == JsonToken.NULL) {
reader.nextNull();
return "";
}
if (reader.peek() == JsonToken.BOOLEAN) {
reader.nextBoolean();
return "";
}
return reader.nextString();
}
public void write(JsonWriter writer, String value) throws IOException {
}
}
public static class IntegerAdapter extends TypeAdapter {
@Override
public void write(JsonWriter out, Integer value) throws IOException {
}
@Override
public Integer read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
in.nextNull();
return -1;
}
//默认的gson其实可以对string转换int,不过仅限string内容为数字,
//为了保证安全,这里统一处理
if (in.peek() == JsonToken.STRING){
in.nextString();
return -1;
}
if (in.peek() == JsonToken.BOOLEAN) {
in.nextBoolean();
return -1;
}
return in.nextInt();
}
}
这里返回-1只是为了验证我们转换是否有作用,然后将这些Adapter添加到TypeAdapterFactory再设置给gson。
public static class MyTypeAdapterFactory implements TypeAdapterFactory {
public TypeAdapter create(Gson gson, TypeToken type) {
Class rawType = (Class) type.getRawType();
if (rawType == String.class)
return (TypeAdapter) new StringAdapter();
if (rawType == int.class)
return (TypeAdapter) new IntegerAdapter();
if (rawType == boolean.class)
return (TypeAdapter)new BooleanAdapter();
if (rawType == double.class)
return (TypeAdapter)new DoubleAdapter();
if (rawType == float.class)
return (TypeAdapter)new FloatAdapter();
return null;
}
}
验证一下
private void customGson() {
GsonBuilder builder = new GsonBuilder();
Gson gson = builder.registerTypeAdapterFactory(new MyTypeAdapterFactory()).create();
UserBean bean = gson.fromJson("{\"address\":false,\"age\":\"sss\",\"email\":null,\"gender\":null,\"hasMarry\":1.5,\"name\":null,\"weight\":false,\"height\":\"188\"}", UserBean.class);
Log.i("kkk", "错误类型:\n");
Log.i("kkk", bean.toString());
UserBean bean1 = gson.fromJson("{\"address\":\"北京\",\"age\":18,\"email\":\"126.com\",\"gender\":\"男\",\"hasMarry\":true,\"name\":\"kjt\",\"weight\":150.0,\"height\":188}", UserBean.class);
Log.i("kkk", "正常类型:\n");
Log.i("kkk", bean1.toString());
}
错误类型:
name:,gender:,email:,address:,age:-1,hasMarry:false,weight:0.0,height:0.0
正常类型:
name:kjt,gender:男,email:126.com,address:北京,age:18,hasMarry:true,weight:150.0,height:188.0
ok,可以看到错误的类型都按照我们约束的值返回了~开心~
如果文中有错误的地方或者想提的意见,希望大家踊跃留言评论~
如果对你有用的话点个赞呗~