I want to serialize nulls for a specific field or class.
In GSON, the option serializeNulls()
applies to the whole JSON.
Example:
Create subclass of com.google.gson.TypeAdapter
and register it for required field using annotation com.google.gson.annotations.JsonAdapter
. Or register it using GsonBuilder.registerTypeAdapter
. In that adapter write
(and read
) should be implemented. For example:
public class JsonTestNullableAdapter extends TypeAdapter<Test> {
@Override
public void write(JsonWriter out, Test value) throws IOException {
out.beginObject();
out.name("name");
out.value(value.name);
out.name("value");
if (value.value == null) {
out.setSerializeNulls(true);
out.nullValue();
out.setSerializeNulls(false);
} else {
out.value(value.value);
}
out.endObject();
}
@Override
public Test read(JsonReader in) throws IOException {
in.beginObject();
Test result = new Test();
in.nextName();
if (in.peek() != NULL) {
result.name = in.nextString();
} else {
in.nextNull();
}
in.nextName();
if (in.peek() != NULL) {
result.value = in.nextString();
} else {
in.nextNull();
}
in.endObject();
return result;
}
}
in MainClass
add JsonAdapter
annotation with the adapter to Test
class field:
public static class MClass {
public String id;
public String name;
@JsonAdapter(JsonTestNullableAdapter.class)
public Test test;
}
the output of System.out.println(new Gson.toJson(mainClass))
is:
{
"id": "101",
"test": {
"name": "testName",
"value": null
}
}
For those looking for a Java version of @Joris's excellent answer, the below code should do the trick. It's largely just a translation of the Kotlin, with a minor improvement to how the serialized name of the attribute is fetched to ensure it always works when the serialized name is different than the attribute name (see the comments on the original answer).
This is the TypeAdapterFactory
implementation:
public class NullableAdapterFactory implements TypeAdapterFactory {
@Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
Field[] declaredFields = type.getRawType().getDeclaredFields();
List<String> nullableFieldNames = new ArrayList<>();
List<String> nonNullableFieldNames = new ArrayList<>();
for (Field declaredField : declaredFields) {
if (declaredField.isAnnotationPresent(JsonNullable.class)) {
if (declaredField.getAnnotation(SerializedName.class) != null) {
nullableFieldNames.add(declaredField.getAnnotation(SerializedName.class).value());
} else {
nullableFieldNames.add(declaredField.getName());
}
} else {
if (declaredField.getAnnotation(SerializedName.class) != null) {
nonNullableFieldNames.add(declaredField.getAnnotation(SerializedName.class).value());
} else {
nonNullableFieldNames.add(declaredField.getName());
}
}
}
if (nullableFieldNames.size() == 0) {
return null;
}
TypeAdapter<T> delegateAdapter = gson.getDelegateAdapter(NullableAdapterFactory.this, type);
TypeAdapter<JsonElement> elementAdapter = gson.getAdapter(JsonElement.class);
return new TypeAdapter<T>() {
@Override
public void write(JsonWriter out, T value) throws IOException {
JsonObject jsonObject = delegateAdapter.toJsonTree(value).getAsJsonObject();
for (String name: nonNullableFieldNames) {
if (jsonObject.has(name) && jsonObject.get(name) instanceof JsonNull) {
jsonObject.remove(name);
}
}
out.setSerializeNulls(true);
elementAdapter.write(out, jsonObject);
}
@Override
public T read(JsonReader in) throws IOException {
return delegateAdapter.read(in);
}
};
}
}
And this is the @JsonNullable
annotation to mark the target attributes:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface JsonNullable {
}
I implemented it as an @JsonAdapter(NullableAdapterFactory.class)
annotation on the object class, rather registering it as a TypeAdapterFactory
on the GsonBuilder
instance, so my object classes looked a bit like this:
@JsonAdapter(NullableAdapterFactory.class)
public class Person {
public String firstName;
public String lastName;
@JsonNullable
public String someNullableInfo;
}
However, the other approach should work just as well with this code if preferred.
I have a solution similar to the one of Aleksey but that can be applied to one or more fields in any class (example in Kotlin):
Create a new annotation for fields that should be serialized as null:
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FIELD)
annotation class SerializeNull
Create a TypeAdapterFactory
that checks if a class has fields annotated with this annotation and removes the fields that are null
and not annotated with the annotation from the JsonTree
when writing the object:
class SerializableAsNullConverter : TypeAdapterFactory {
override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
fun Field.serializedName() = declaredAnnotations
.filterIsInstance<SerializedName>()
.firstOrNull()?.value ?: name
val declaredFields = type.rawType.declaredFields
val nullableFieldNames = declaredFields
.filter { it.declaredAnnotations.filterIsInstance<SerializeNull>().isNotEmpty() }
.map { it.serializedName() }
val nonNullableFields = declaredFields.map { it.serializedName() } - nullableFieldNames
return if (nullableFieldNames.isEmpty()) {
null
} else object : TypeAdapter<T>() {
private val delegateAdapter = gson.getDelegateAdapter(this@SerializableAsNullConverter, type)
private val elementAdapter = gson.getAdapter(JsonElement::class.java)
override fun write(writer: JsonWriter, value: T?) {
val jsonObject = delegateAdapter.toJsonTree(value).asJsonObject
nonNullableFields
.filter { jsonObject.get(it) is JsonNull }
.forEach { jsonObject.remove(it) }
val originalSerializeNulls = writer.serializeNulls
writer.serializeNulls = true
elementAdapter.write(writer, jsonObject)
writer.serializeNulls = originalSerializeNulls
}
override fun read(reader: JsonReader): T {
return delegateAdapter.read(reader)
}
}
}
}
Register the adapter with your Gson instance:
val builder = GsonBuilder().registerTypeAdapterFactory(SerializableAsNullConverter())
And annotate the fields you would like to be nullable:
class MyClass(val id: String?, @SerializeNull val name: String?)
Serialization result:
val myClass = MyClass(null, null)
val gson = builder.create()
val json = gson.toJson(myClass)
json:
{
"name": null
}
I took a few ideas from various answers here.
this implementation:
JsonNullable.isJsonNull() == true
JsonNullable.isJsonNull() == false
Parent
containing JsonNullable
is null
delegateAdapter
by using a TypeAdapterFactory
objects that may need to be serialized to null implement this interface
/**
* [JsonNullableTypeAdapterFactory] needs to be registered with the [com.google.gson.Gson]
* serializing implementations of [JsonNullable] for [JsonNullable] to work.
*
* [JsonNullable] allows objects to choose at runtime whether they should be serialized as "null"
* serialized normally, or be omitted from the JSON output from [com.google.gson.Gson].
*
* when [isJsonNull] returns true, the subclass will be serialized to a [com.google.gson.JsonNull].
*
* when [isJsonNull] returns false, the subclass will be serialized normally.
*/
interface JsonNullable {
/**
* return true to have the entire object serialized as `null` during JSON serialization.
* return false to have this object serialized normally.
*/
fun isJsonNull(): Boolean
}
type adapter factory that serializes values to null
class JsonNullableTypeAdapterFactory : TypeAdapterFactory {
override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
return object : TypeAdapter<T>() {
private val delegateAdapter = gson.getDelegateAdapter(this@JsonNullableTypeAdapterFactory, type)
override fun read(reader: JsonReader): T = delegateAdapter.read(reader)
override fun write(writer: JsonWriter, value: T?) {
if (value is JsonNullable && value.isJsonNull()) {
val originalSerializeNulls = writer.serializeNulls
writer.serializeNulls = true
writer.nullValue()
writer.serializeNulls = originalSerializeNulls
} else {
delegateAdapter.write(writer, value)
}
}
}
}
}
register thr type adapter factroy with GSON
new GsonBuilder()
// ....
.registerTypeAdapterFactory(new JsonNullableTypeAdapterFactory())
// ....
.create();
example object that gets serialized to JSON
data class Parent(
val hello: Child?,
val world: Child?
)
data class Child(
val name: String?
) : JsonNullable {
override fun isJsonNull(): Boolean = name == null
}
I have interface to check when object should be serialized as null:
public interface JsonNullable {
boolean isJsonNull();
}
And the corresponding TypeAdapter (supports write only)
public class JsonNullableAdapter extends TypeAdapter<JsonNullable> {
final TypeAdapter<JsonElement> elementAdapter = new Gson().getAdapter(JsonElement.class);
final TypeAdapter<Object> objectAdapter = new Gson().getAdapter(Object.class);
@Override
public void write(JsonWriter out, JsonNullable value) throws IOException {
if (value == null || value.isJsonNull()) {
//if the writer was not allowed to write null values
//do it only for this field
if (!out.getSerializeNulls()) {
out.setSerializeNulls(true);
out.nullValue();
out.setSerializeNulls(false);
} else {
out.nullValue();
}
} else {
JsonElement tree = objectAdapter.toJsonTree(value);
elementAdapter.write(out, tree);
}
}
@Override
public JsonNullable read(JsonReader in) throws IOException {
return null;
}
}
Use it as follows:
public class Foo implements JsonNullable {
@Override
public boolean isJsonNull() {
// You decide
}
}
In the class where Foo value should be serialized as null. Note that foo value itself must be not null, otherwise custom adapter annotation will be ignored.
public class Bar {
@JsonAdapter(JsonNullableAdapter.class)
public Foo foo = new Foo();
}