Gson反序列化中的NULL值替换(各种类型)

相信很多移动开发在后台开发接口时,曾一遍遍的发生过如下场景~

移动端:

不要传null!不要传null!不要传null!

后台:

放心吧,大兄弟,我怎么会传null给你呢(滑稽脸)

结果在对接口时才发现,曾经的誓言都喂了狗,这些null就像垃圾短信一样,时不时的蹦出来骚扰你,轻则影响UI展示,重则导致应用崩溃。于是你怒气冲冲的跑去质问后台,发生如下场景。。

移动端:

为什么要骗我?说好的永不传null呢?难道我们不是最好的朋友吗(滑稽脸)?

后台:

不能啊,怎么会传null呢,我明明改过了啊,你先别急,大兄弟,我们当然是最好的朋友啦(滑稽脸),我看看哪里出了问题。

---------------------------------------------------------------------------------------------------------十年后

后台:哦哦,我漏掉了一个东西,balabala......,放心吧,大兄弟,不会传null了,再传null,你拿刀来见我!

移动端:

哦(我信你个鬼,你个糟老头子坏得很)。

那么,问题来了,如何在和一个不靠谱的后台开发合作时,保证自己的应用不会出现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值替换

先看看我们什么都不做,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反序列化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}。
   */
  <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type);
}

其实这个接口的注释有很长很长一大段,包含示例和解释,想看的可以去源码里看一下,这里我只挑重点放上。

首先这个接口是在Gson2.1才开始有的,所以Gson版本低于2.1的赶紧升级一下版本,这个接口是Gson的类型适配器工厂,里面的方法作用是”返回对应类型的适配器“,如果我们想在反序列化其他对象类型时做一些操作,可以通过这个适配器来完成。比如把字符串全部转换小写,对列表元素做一些预处理等等,所以,我们是不是可以通过它对类型为String的null值做一些操作呢?来试试!

仿照示例代码编写一个StringAdapterFactory和StringAdapter

public static class StringAdapterFactory implements TypeAdapterFactory {
        public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {

            Class<T> rawType = (Class<T>) type.getRawType();
            //如果对象类型为String,返回自己实现的StringAdapter
            if (rawType != String.class) {
                return null;
            }
            return (TypeAdapter<T>) new StringAdapter();
        }
    }

    public static class StringAdapter extends TypeAdapter<String> {
        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字符串操作导致的异常崩溃了✌️

Gson反序列化错误类型处理

本以为高高兴兴,解决了后台传null的问题,没想到刚过几天安稳日子,程序又崩了!一查原因,好嘛,魔高一尺道高一丈,我升级,后台也升级,这下不光传null了,类型都传错了,我要int,他却传给我一个string,我要string,他给我传过来一个boolean。于是拿着这个问题又和后台人员亲切交流一番,本着防患于未然,我觉得还是在App里加一个处理比较稳妥,这样,下次再有这种情况,起码不影响App的正常使用,防止崩溃。

怎么做呢?还是通过那个Adapter,除了对null判断,再加上对其他类型的判断,比如对string和int类型的值校验,其他基本类型的校验一样,可以照着这两个写:

public static class StringAdapter extends TypeAdapter<String> {
        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<Integer> {

        @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 <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {

            Class<T> rawType = (Class<T>) type.getRawType();
            if (rawType == String.class) 
                return (TypeAdapter<T>) new StringAdapter();
            if (rawType == int.class) 
                return (TypeAdapter<T>) new IntegerAdapter();
            if (rawType == boolean.class)
                return (TypeAdapter<T>)new BooleanAdapter();
            if (rawType == double.class)
                return (TypeAdapter<T>)new DoubleAdapter();
            if (rawType == float.class)
                return (TypeAdapter<T>)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,可以看到错误的类型都按照我们约束的值返回了~开心~

如果文中有错误的地方或者想提的意见,希望大家踊跃留言评论~

如果对你有用的话点个赞呗~

发布了45 篇原创文章 · 获赞 18 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/Ever69/article/details/86469320