Home » Android » android – GSON: Serialize a single null field without using the global serializeNulls()

android – GSON: Serialize a single null field without using the global serializeNulls()

Posted by: admin May 14, 2020 Leave a comment

Questions:

I’m using Gson with Retrofit, and I have a case where I need to serialize a single null field, but I cannot turn on the global serializeNulls() flag for Gson, because it will break the rest of my code. Does anyone know how to accomplish this?

Here’s what I’ve tried:

  • Make an annotation, @SerializeNull. This would be the ideal solution, but it failed because the serializeNulls flag is actually on the JsonWriter, which is downstream of the ExclusionStrategy.
  • Use a TypeAdapterFactory. Again, the JsonWriter is downstream.

I also couldn’t figure out a way to add to the serialized json, since it all happens inside Retrofit.

How to&Answers:

I solved this problem by setting setSerializeNulls(true) and restoring original value to JsonWriter in TypeAdapter.write():

public class NullableDoubleAdapter extends TypeAdapter<NullableDouble> {

  @Override
  public void write(final JsonWriter out, final NullableDouble value) throws IOException {
    if (value == null) {
      boolean serializeNulls = out.getSerializeNulls();
      out.setSerializeNulls(true);
      out.nullValue();
      out.setSerializeNulls(serializeNulls);
    } else {
      out.value(value.getValue());
    }
  }
...

Answer:

Here’s an extension of Andriy’s solution that doesn’t just work for NullableDouble, but allows generically wrapping any type. The downside compared to plain serializeNulls() is that it requires creating the GsonForceNull instance as a wrapper and accessing the value inside throughout the Java code (though suitable getter/setter implementations can hide that detail).

import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;

import java.io.IOException;
import java.lang.reflect.ParameterizedType;

// Forces Gson to output `null` in case the value is null, rather than omitting the field altogether.
public class GsonForceNull<V> {
    public V value;

    public GsonForceNull() {
    }

    public GsonForceNull(V value) {
        this.value = value;
    }

    static class ForceNullTypeAdapterFactory implements TypeAdapterFactory {
        @Override
        public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
            if (type.getRawType() != GsonForceNull.class) return null;
            //noinspection unchecked
            return this.createInternal(gson,
                    (TypeToken) TypeToken.get(((ParameterizedType) type.getType()).getActualTypeArguments()[0]));
        }

        private <V> TypeAdapter<GsonForceNull<V>> createInternal(Gson gson, TypeToken<V> innerType) {
            final TypeAdapter<V> delegate = gson.getDelegateAdapter(this, innerType);
            return new TypeAdapter<GsonForceNull<V>>() {
                @Override
                public void write(JsonWriter out, GsonForceNull<V> value) throws IOException {
                    V innerValue = value == null ? null : value.value;
                    if (innerValue == null && !out.getSerializeNulls()) {
                        out.setSerializeNulls(true);
                        out.nullValue();
                        out.setSerializeNulls(false);
                    } else {
                        delegate.write(out, innerValue);
                    }
                }

                @Override
                public GsonForceNull<V> read(JsonReader in) throws IOException {
                    return new GsonForceNull<>(delegate.read(in));
                }
            };
        }
    }
}

Example usage:

gsonBuilder.registerTypeAdapterFactory(new GsonForceNull.ForceNullTypeAdapterFactory());

class MyExampleRequestClass {
    private final GsonForceNull<Long> someField = new GsonForceNull<>();
    // getter/setter
}