public static void main(String[] args) {
String tr = "[{\"key\":\"foo\"}, {\"key\":\"shoe\"}]";
List<MyClass> o = new Gson().fromJson(tr, getType());
for (MyClass object : o) {
System.out.println(object);
}
}
public static <T extends MyClass> Type getType() {
return new TypeToken<List<T>>() {
}.getType();
}
public static class MyClass {
private String key;
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
@Override
public String toString() {
return "X{" +
"key='" + key + '\'' +
'}';
}
}
I am trying to understand how Gson works with Generics. I am getting error in above main method.
Exception in thread "main" java.lang.ClassCastException: class com.google.gson.internal.LinkedTreeMap cannot be cast to class org.example.Main$MyClass (com.google.gson.internal.LinkedTreeMap and org.example.Main$MyClass are in unnamed module of loader 'app')
Due to Type Erasure shouldn't T be set to MyClass in the method getType(). So, Gson should have created an instance of List<MyClass>
instead of List<Object>
? Why T is getting treated as an Object here instead of MyClass?
I tried runnig this code but it is giving me error.
You are right that this behavior does not match normal Java type erasure. However, Gson does actually see the type variable T
and then itself performs (incorrect) type erasure on it. Gson currently ignores the bounds of type variables and always uses Object
instead, see issue 2563.
This leads to the exception you are seeing: Gson deserializes the value as Object
, and because it is a JSON object Gson creates a Map
, more specifically an instance of its internal LinkedTreeMap
class.
As side note, your code contains two non type-safe actions:
Creating a TypeToken<List<T>>
As you mention at runtime due to type erasure Gson cannot know the actual type of T
. So if your example had a MySubclass extends MyClass
, then even if Gson's type erasure was correct you could still see a ClassCastException
when you call <MySubclass>getType()
. Because at runtime this information is not available so it would actually be a TypeToken<List<T extends MyClass>>
, erased to TypeToken<List<MyClass>>
instead of TypeToken<List<MySubclass>>
.
Due to that future Gson versions will disallow capturing type variables by default.
Using Gson.fromJson(..., Type)
These methods cannot enforce type-safety and allow you to write something like the following without causing any compilation warning or error:
List<MyClass> l = gson.fromJson(json, new TypeToken<List<WrongClass>>() {}.getType());
(note the mismatch MyClass
vs. WrongClass
)
You should prefer the type-safe Gson.fromJson(..., TypeToken<T>)
overloads instead, added in Gson 2.10. That is, simply omit the TypeToken.getType()
call.
A type-safe variant of your getType()
method could look like this:
private static <T extends MyClass> TypeToken<List<T>> getType(Class<T> elementClass) {
return (TypeToken<List<T>>) TypeToken.getParameterized(List.class, elementClass);
}
In Kotlin your original example could also work if you make the type variable reified
and change the return type from Type
to TypeToken<List<T>>
.