javagsontype-erasure

Java Type Erasure during GSON


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.


Solution

  • 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:

    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>>.