需求
公司的开发中遇到这样一个需求:
在给后台发送的所有网络请求中,body参数中如果有字段的值是空字符串,就去掉这个字段,不发送给后台。
举个例子,假设我们要通过body发送的数据长这样:
{
"param1":"1",
"param2":"2",
"param3":"3",
"param4":"",
"param5":null,
}
实际发送的参数:
修改前:
{
"param1":"1",
"param2":"2",
"param3":"3",
"param4":""
}
修改后:
{
"param1":"1",
"param2":"2",
"param3":"3"
}
从例子中可以看出,我们要实现的功能就是在Retrofit发送请求的时候,将body中的值为空串的参数去掉。
在指定ConverterFactory为Gson之后,Retrofit默认会把值为 null
的参数去掉,但是并不会去掉值为空串的参数,所以我们要自己处理,去掉值为空串的参数。
指定ConverterFactory为默认的GsonConverterFactory的代码:
Retrofit retrofit = new Retrofit.Builder() .baseUrl("https://wwww.baidu.com") .addConverterFactory(GsonConverterFactory.create()) .build();
实现
解决办法其实不难,就是利用Retrofit的Converter机制。
我们项目中利用的是GsonConverter:
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
也就是说,我们的对象的序列化与反序列化是使用Gson的对象。那么下一步自然就是想到如何通过配置GsonConverter来实现这个需求。
其实这个需求实现起来很简单,代码如下:
TypeAdapter<String> stringTypeAdapter = new TypeAdapter<String>() {
@Override
public String read(JsonReader in) throws IOException {
JsonToken peek = in.peek();
if (peek == JsonToken.NULL) {
in.nextNull();
return null;
}
/* coerce booleans to strings for backwards compatibility */
if (peek == JsonToken.BOOLEAN) {
return Boolean.toString(in.nextBoolean());
}
return in.nextString();
}
@Override
public void write(JsonWriter out, String value) throws IOException {
// 下面这个if是关键代码,指定序列化时,遇到空串则直接输出null值。
// 由于Gson默认是不序列化null的,所以这里就相当于在序列化时排除了空串的字段
if ("".equals(value)) {
out.nullValue();
return;
}
out.value(value);
}
};
Gson gson = new GsonBuilder().registerTypeAdapter(String.class, stringTypeAdapter).create();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://wwww.baidu.com")
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
关键代码如代码中注释所述。
这里的思路就是,指定Gson
的TypeAdapter
,为String
类型的变量单独指定序列化与反序列化的规则。
至于这个String
类型的TypeAdapter
的写法,实际上是参考了Gson
对象的源码。
思考
初遇到这个需求的时候,我大概知道方向,但是具体的细节我也是不知道要如何实现的。我只知道Retrofit
可以自己定义Converter
,帮助我们实现序列化与反序列化。但是具体怎么去实现不去序列化String
类型的空串,我也是不知道的。
我的思路是:
首先,GsonConverterFactory.create()方法有一个带参数的方法,GsonConverterFactory.create(Gson gson)
,那我们就可以通过为Factory
配置实现了我们需求的gson
对象来实现这个需求。
其次,Gson
默认不序列化null
类型的对象。这是如何实现的?如果搞清楚了这个,我感觉就可以用同样的方式让Gson
不序列化空串。
然后,基于上面的想法,我开始看Gson
的源代码。看到了下面这一段:
public static final TypeAdapterFactory STRING_FACTORY = newFactory(String.class, STRING);
这里定义了一个TypeAdapterFactory
,明显是用来处理String
类型的。后面传入newFactory
方法中的STRING
对象的定义如下:
public static final TypeAdapter<String> STRING = new TypeAdapter<String>() {
@Override
public String read(JsonReader in) throws IOException {
JsonToken peek = in.peek();
if (peek == JsonToken.NULL) {
in.nextNull();
return null;
}
/* coerce booleans to strings for backwards compatibility */
if (peek == JsonToken.BOOLEAN) {
return Boolean.toString(in.nextBoolean());
}
return in.nextString();
}
@Override
public void write(JsonWriter out, String value) throws IOException {
out.value(value);
}
};
看到这里,终于知道,其实我们要做的就是重新定义这里的 write
方法中的内容。
由于Gson默认是不序列化null
的,所以我们可以在字符串为空时,指定输出一个null
值。这样在默认情况下,就可以达到不输出空串的目标了。
篇幅有限,实际上自己看的和查的要比上面写的内容多很多,但是上面的内容是关键点,其他的细枝末节相信大家以自己的聪明才智也可以搞得定。因为Gson的源码本身难度并不高。