Retrofit2使用@Body提交数据时请求体中值为null的字段为什么被自动过滤?

840 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第27天,点击查看活动详情

今天在和后端联调接口的时候,接口请求体中有一个字段是非必需的,也就是有的场景下需要这个字段,有的场景下不需要。但是后端同学又要求不需要的情况下传一个null过去。

也就是接口提交的数据需要如下所示:

{
  "subject": null
}

行,既然这样要求,那就这样做吧。

项目中使用Retrofit2+RxJava进行网络请求。

定义接口请求方法:

@POST("xxx/activated_list")
fun fetchBookshelf(@Body request: BookshelfRequest): Observable<FetcherResponse<BookshelfResponse>>

请求体如下所示:

data class BookshelfRequest(val subject: Int? = null)

然后开始请求数据,在不需要传值的情况下请求数据:

presenter.fetchBookshelf(BookshelfRequest(null))

本以为万事大吉了,但是数据并没有正常请求,抓接口发现,提交的json数据中并没有subject这个字段:

{}

分析原因发现,是Gson在将请求体BookshelfRequest转成json串的时候,将值为null的字段过滤了,没有参加序列化。

首先看下Retrofit的使用步骤。

创建Retrofit,调用addConverterFactory()方法传入一个转换工厂。

@Override
protected Retrofit createRetrofit(OkHttpClient okHttpClient) {
    Retrofit retrofit = super.createRetrofit(okHttpClient);
    return retrofit.newBuilder()
        //这里会给Retrofit传一个自定义Converter.Factory,用来做数据实体和json串之间的转换
        .addConverterFactory(CustomGsonConverterFactory.create(getResponseFilterValid()))
        .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
        .build();
}

CustomGsonConverterFactory是自定义的ConverterFactory,继承自Retrofit的Converter.Factory

public class CustomGsonConverterFactory extends Converter.Factory {
    private final Gson mGson;
    private final IResponseFilterValid mResponseFilterValid;

    /**
     * Create an instance using a default {@link Gson} instance for conversion. Encoding to JSON and
     * decoding from JSON (when no charset is specified by a header) will use UTF-8.
     */
    public static CustomGsonConverterFactory create(IResponseFilterValid responseFilterValid) {
        //接口要求请求体的json串的字段值为null也要提交过去,就像这样{"subject": null}
        //而Retrofit使用@Body提交入参后,Gson默认会过滤掉值为null的字段,所以这里需要给Gson加上.serializeNulls()方法
        //表示值为mull的字段也会参与序列化,这样传递给后台的json串就是为{"subject": null}了
        Gson gson = new GsonBuilder().serializeNulls().create();
        return create(gson, responseFilterValid);
    }
    
    ...//省略代码
}

可以看到这里创建了一个Gson对象,解决本文中问题的关键就是调用一下GsonBuilder().serializeNulls()方法。

看下GsonBuilderserializeNulls()方法。

public GsonBuilder serializeNulls() {
    this.serializeNulls = true;
    return this;
}

调用serializeNulls()之后,GsonBuilder()中的serializeNulls被赋值为true。然后通过GsonBuilder.create()方法创建GsonserializeNulls通过Gson的构造方法传入Gson类。

public Gson create() {
    List<TypeAdapterFactory> factories = new ArrayList<TypeAdapterFactory>();
    factories.addAll(this.factories);
    Collections.reverse(factories);
    factories.addAll(this.hierarchyFactories);
    addTypeAdaptersForDate(datePattern, dateStyle, timeStyle, factories);
    //通过GsonBuilder的create方法创建Gson,通过Gson的构造方法将serializeNulls传入
    return new Gson(excluder, fieldNamingPolicy, instanceCreators,
                    serializeNulls, complexMapKeySerialization,
                    generateNonExecutableJson, escapeHtmlChars, prettyPrinting, lenient,
                    serializeSpecialFloatingPointValues, longSerializationPolicy, factories);
  }

接着在Gson.toJson()方法中,将serializeNulls传入JsonWriter

public void toJson(Object src, Type typeOfSrc, JsonWriter writer) throws JsonIOException {
    TypeAdapter<?> adapter = getAdapter(TypeToken.get(typeOfSrc));
    boolean oldLenient = writer.isLenient();
    writer.setLenient(true);
    boolean oldHtmlSafe = writer.isHtmlSafe();
    writer.setHtmlSafe(htmlSafe);
    boolean oldSerializeNulls = writer.getSerializeNulls();
    //将serializeNulls传入JsonWriter
    writer.setSerializeNulls(serializeNulls);
    try {
      ((TypeAdapter<Object>) adapter).write(writer, src);
    } catch (IOException e) {
      throw new JsonIOException(e);
    } finally {
      writer.setLenient(oldLenient);
      writer.setHtmlSafe(oldHtmlSafe);
      writer.setSerializeNulls(oldSerializeNulls);
    }
  }

然后在JsonWriternullValue()方法中,根据serializeNulls的值去判断值为null的字段参加不参加序列化。

  public JsonWriter nullValue() throws IOException {
    if (deferredName != null) {
      if (serializeNulls) {
        writeDeferredName();
      } else {
        deferredName = null;
        return this; // skip the name and the value
      }
    }
    beforeValue();
    out.write("null");
    return this;
  }

所以,在创建Gson的时候调用GsonBuilder().serializeNulls()即可不过滤值为null的字段。


关注木水小站 (zhangmushui.cn)和微信公众号【木水Code】,及时获取更多最新技术干货。