Joffrey Joffrey - 1 month ago 27
Java Question

Gson custom deserializer not called for null values

I'm using Gson serialization in a JavaFX context. To avoid having property boilerplate in my serialized Json, I created a handful of custom serializers/deserializers to write the property's value instead of the actual property object, in particular this one for the generic

Property<T>
:

private static class PropertySerializer implements JsonSerializer<Property<?>> {
@Override
public JsonElement serialize(Property<?> property, Type type, JsonSerializationContext context) {
Object value = property.getValue();
return context.serialize(value);
}
}

private static class PropertyDeserializer implements JsonDeserializer<Property<?>> {
@Override
public Property<?> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
if (json.isJsonNull()) {
System.out.println("I am never printed");
}
Type typeParam = ((ParameterizedType) typeOfT).getActualTypeArguments()[0];
Object obj = context.deserialize(json, typeParam);
return new SimpleObjectProperty<>(obj);
}
}


It worked pretty well until I stumbled upon null values. By design, when the property's value is null (not the property itself), my serializer will write a null because it writes the value, not the property (I have used
Gson.serializeNulls()
to make it actually write it).

My problem is that, at deserialization time, my custom serializer is not called for null values because of a Gson bug caused by these lines in
com.google.gson.TreeTypeAdapter
:

@Override public T read(JsonReader in) throws IOException {
if (deserializer == null) {
return delegate().read(in);
}
JsonElement value = Streams.parse(in);
if (value.isJsonNull()) {
return null; // bypasses my custom deserializer! Why you do dat to me?
}
return deserializer.deserialize(value, typeToken.getType(), gson.deserializationContext);
}


This results in the property itself being null after deserialization instead of a non-null property containing a null value.




They marked the bug as fixed because apparently there is a workaround using a
TypeAdapterFactory
, but I can't seem to figure out how to do it. It looks like I have to manually deserialize the object, but I don't know its type so I can't. The
TypeAdapter
methods
read
and
write
don't provide me with the
JsonDeserializationContext
of my current
Gson
, so I can't rely on its standard deserialization for my value (which I did very easily with the custom deserializer).

private static class PropertyTypeAdapter extends TypeAdapter<Property<?>> {

@Override
public void write(JsonWriter out, Property<?> value) throws IOException {
Object valueToSerialize = value.getValue();
// how to use standard serialization of my object?
}

@Override
public Property<?> read(JsonReader in) throws IOException {
switch (in.peek()) {
case NULL:
return new SimpleObjectProperty<>(null);
default:
Type typeOfT = ???
Type typeParam = ((ParameterizedType) typeOfT).getActualTypeArguments()[0];
return ???; // how to invoke standard deserialization of my object?
}
}
}


Does anyone know how to solve this?

Answer

For future readers: I didn't like the solution I implemented at first in my other answer, so I changed it to use TypeAdapter instead of a custom deserializer.

This takes more code to write, so I ended up writing a lightweight library tailored to use Gson in the context of JavaFX, so that no one has to write it again :)

Here it is: FxGson.