I'm writing a class which will connect to a server and based on some arguments, retrieve a json-string which will be parsed with GSON to the specified (via generics) class.
A stripped down version of the class in charge looks like this:
class Executor<T> {
private Response<T> response;
public void execute() {
Type responseType = new TypeToken<Response<T>>() {}.getType();
this.response = new Gson().fromJson(json, responseType);
}
public Response<T> getResponse() { return this.response; }
}
(the JSON
-variable looks like this.)
The class which stores the data once de-serialized looks like this:
class Response<T> {
private List<T> data = null;
public List<T> getData() { return this.data; }
}
The class which the data is trying to be de-serialized to:
public class Language {
public String alias;
public String label;
}
And the code which runs utilizes the classes above:
Executor<Language> executor = new Executor<Language();
List<Language> languages = executor.execute().getResponse().getData();
System.out.println(languages.get(0).alias); // exception occurs here
Which results in the following exception
ClassCastException: com.google.gson.internal.StringMap cannot be cast to sunnerberg.skolbibliotek.book.Language
Any help or suggestions are greatly appreciated!
The short answer is that you need to move the creation of the
TypeToken
out ofExecutor
, bind theT
inResponse<T>
when you create the token (new TypeToken<Response<Language>>() {}
), and pass in the type token to theExecutor
constructor.The long answer is:
Generics on a type are typically erased at runtime, except when the type is compiled with the generic parameter bound. In that case, the compiler inserts the generic type information into the compiled class. In other cases, that is not possible.
So for instance, consider:
At runtime, Java knows
bar
contains integers because theInteger
type is bound toArrayList
at compile time, so the generic type information is saved in theIntegerList
class file. However, the generic type information forfoo
is erased, so at runtime it is not really possible to determine thatfoo
is supposed to containInteger
s.So it often comes up that we need generic type information in a situation where it normally would be erased before runtime, such as here in the case of parsing JSON data in GSON. In these situations, we can take advantage of the fact that type information is preserved when it is bound at compile-time (as in the
IntegerList
example above) by using type tokens, which are really just tiny anonymous classes that conveniently store generic type information.Now to your code:
In this line of your
Executor
class, we create an anonymous class (inheriting fromTypeToken
) which has the typeResponse<T>
hard coded (bound) at compile-time. So at runtime, GSON is able to determine that you want an object ofResponse<T>
. But it doesn't know whatT
is, because you didn't specify it at compile-time! So GSON cannot determine what type will be in theList
of theResponse
object it creates, and it just creates aStringMap
instead.The moral of the story is that you need to specify that
T
at compile-time. IfExecutor
is meant to be used generically, you probably need to create the type token outside of that class, in your client code. Something like:By the way, I did test the above on my machine.
Sorry if that was too long!
Your problem is linked to java Type erasure. Generics are only known at compile time.
So sadly, there is no way for GSON to know to which class it should deserialize, using reflection.
You haven't called
response.execute();
afterExecutor<Language> executor = new Executor<Language>();
this statement. You can't utilize the java generics here, but you can get the same effect with the following code.Response.java
Language.java
Finally, the Executor.java
You can test it as follows