Why does it seem that Gson ignores the nested generic type declaration when serializing?
I am trying to get Gson to use the compile-time type I specify, instead of the runtime type of objects in the list. I am also using an abstract superclass for A.java
, but the example below has the same problem.
public class A {
public String foo;
}
public class B extends A {
public String bar;
}
public static void main( String[] args ) {
Gson gson = new Gson();
B b = new B();
b.foo = "foo";
b.bar = "bar";
List<A> list = new ArrayList<A>();
list.add(b);
System.out.println(gson.toJson(b, new TypeToken<A>(){}.getType()));
System.out.println(gson.toJson(b, new TypeToken<B>(){}.getType()));
System.out.println(gson.toJson(list, new TypeToken<List<A>>(){}.getType()));
System.out.println(gson.toJson(list, new TypeToken<List<B>>(){}.getType()));
}
Output:
{"foo":"foo"}
{"bar":"bar","foo":"foo"}
[{"bar":"bar","foo":"foo"}]
[{"bar":"bar","foo":"foo"}]
Expected:
{"foo":"foo"}
{"bar":"bar","foo":"foo"}
[{"foo":"foo"}]
[{"bar":"bar","foo":"foo"}]
Gson is a Java library that can be used to convert Java Objects into their JSON representation. It can also be used to convert a JSON string to an equivalent Java object.
Gson is the main actor class of Google Gson library. It provides functionalities to convert Java objects to matching JSON constructs and vice versa. Gson is first constructed using GsonBuilder and then toJson(Object) or fromJson(String, Class) methods are used to read/write JSON constructs.
GSON has been successfully used in several android apps that are in Google Play today. The benefit you get with GSON is that object mapping can save the time spent writing code.
Gson (also known as Google Gson) is an open-source Java library to serialize and deserialize Java objects to (and from) JSON.
Scroll to the bottom if you don't care about why and just want a fix.
Gson's default CollectionTypeAdapterFactory
wraps it's element type adapters in something called a TypeAdapterRuntimeTypeWrapper
. When choosing the proper adapter, it uses the following priorities:
// Order of preference for choosing type adapters
// First preference: a type adapter registered for the runtime type
// Second preference: a type adapter registered for the declared type
// Third preference: reflective type adapter for the runtime type (if it is a sub class of the declared type)
// Fourth preference: reflective type adapter for the declared type
In this case, the Third preference is an adapter for B
, and the Fourth preference is an adapter for A. This is not avoidable when using the default serializers, as there's no conditional in the CollectionTypeAdapterFactory
:
public Adapter(Gson context, Type elementType,
TypeAdapter<E> elementTypeAdapter,
ObjectConstructor<? extends Collection<E>> constructor) {
this.elementTypeAdapter =
new TypeAdapterRuntimeTypeWrapper<E>(context, elementTypeAdapter, elementType);
this.constructor = constructor;
}
This wrapper is not present when not using the CollectionTypeAdapterFactory
, which is why it doesn't happen in your first two examples.
The only way to get around this problem is to register a custom serializer. Writing one for A
will do the trick, in your use case:
public class ATypeAdapter extends TypeAdapter<A> {
public A read(JsonReader reader) throws IOException {
if (reader.peek() == JsonToken.NULL) {
reader.nextNull();
return null;
}
reader.beginObject();
String name = reader.nextName();
if(!"foo".equals(name)) throw new JsonSyntaxException("Expected field named foo");
A a = new A();
a.foo = reader.nextString();
reader.endObject();
return a;
}
public void write(JsonWriter writer, A value) throws IOException {
if (value == null) {
writer.nullValue();
return;
}
writer.beginObject();
writer.name("foo");
writer.value(value.foo);
writer.endObject();
}
}
Then, if you do:
public static void main( String[] args ) {
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(new TypeToken<A>(){}.getType(), new ATypeAdapter());
Gson gson = builder.create();
B b = new B();
b.foo = "foo";
b.bar = "bar";
List<A> list = new ArrayList<A>();
list.add(b);
System.out.println(gson.toJson(b, new TypeToken<A>(){}.getType()));
System.out.println(gson.toJson(b, new TypeToken<B>(){}.getType()));
System.out.println(gson.toJson(list, new TypeToken<List<A>>(){}.getType()));
System.out.println(gson.toJson(list, new TypeToken<List<B>>(){}.getType()));
}
You get the expected output:
{"foo":"foo"}
{"bar":"bar","foo":"foo"}
[{"foo":"foo"}]
[{"bar":"bar","foo":"foo"}]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With