4

If I register a JsonSerializer with an interface or abstract class like:

GsonBuilder builder = new GsonBuilder()
    .registerTypeAdapter(MyInterface.class, new MySerializer());
Gson gson = builder.create();

MyInterface i = new MyConcreteClass();
String json = gson.toJson(i); // <--- Serializer is not invoked

The serializer is simply not invoked in gson.toJson().

However, if I have a class that contains MyInterface:

public class MyAnotherClass {
    MyInterface i;
    // ...
}

MyAnotherClass c = new MyAnotherClass()
String json = gson.toJson(c); // <--- Serializer is invoked

The serializer is invoked.

To me, this is kind of inconsistent behavior. The way it works on member variable is to look for the declared type instead of actual type, but the way it works on the "root" object is different.

Is this a bug / defect or an expected behavior? If this is expected, any reason behind?

1 Answer 1

17

This is an expected behaviour. Gson has a list of default TypeAdapterFactory objects to use. You can see them in the source of the Gson class.

There are adapters for String, Number, Map, Collection... The last one is important and is used when no adapters can handle a type: it is the ReflectiveTypeAdapter. This one retrieves by reflection all the fields of the object to serialize, and searches for the appropriate adapters to serialize the values of the fields.

So when Gson has to serialize an object, first it tries to find an adapter by using the actual type of the object. If the adapter found is not the ReflectiveTypeAdapter, it uses it. Otherwise it searches an adapter with the declarative type and uses it if it's not the ReflectiveTypeAdapter. Otherwise it uses the ReflectiveTypeAdapter on the object (you can look at the source of the class TypeAdapterRuntimeTypeWrapper to have more details on this mecanism). That's the behaviour you have with your attribute MyInterface i.

When you do:

MyInterface i = new MyConcreteClass();
String json = gson.toJson(i);

Gson tries to find an adapter for the type MyConcreteClass and it finds the ReflectiveTypeAdapter. So now it should search for an adapter with the declarative type. But it has no way to know the declarative type because no object references it, this is the root object. You know in your code that you want to use the type MyInterface, but Gson has not this information and has only the object instance. You may think that Gson could see that the object implements MyInterface, but that's not the way it works (and what to do if the object implements two interfaces?)

So, you have two ways to solve your problem:

1) When you call Gson.toJson, you can give the declarative type for the root object: Gson#toJson(Object, Type)

String json = gson.toJson(i, MyInterface.class);

2) When you register your adapter, you can indicate that this adapter applies to all the subclasses: GsonBuilder#registerTypeHierarchyTree

GsonBuilder builder = new GsonBuilder()
    .registerTypeHierarchyAdapter(MyInterface.class, new MySerializer());

Hope it helps

Sign up to request clarification or add additional context in comments.

3 Comments

Thanks for the answer. Still, I think it is an expected defect due to the limitation of Java.
When I use registerTypeHierarchyAdapter I'm stuck in a continuous loop when the serialize method executes result.add("properties", context.serialize(src, src.getClass()))
I tried your 1st solution and it gives me empty json {}

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.