Frage

I'm using Google's JSON library called Gson in one of my project.

I have a code for converting JSON String into object using GSON. I have following method to do that:

public static <T> ApiResponse<T> fromJson(String json)
{
    return new Gson().fromJson(json, new TypeToken<ApiResponse<T>>() {}.getType());
}

And it seems to work fine when I do something like that:

ApiResponse<List<JobModel>> response = ApiResponse.fromJson(new String(bytes));

OR

ApiResponse<Double> response = ApiResponse.fromJson(new String(bytes));

But when I try do this:

ApiResponse<JobModel> response = ApiResponse.fromJson(new String(bytes));

Where JobModel is my own class I get the following error:

com.google.gson.internal.LinkedTreeMap cannot be cast to com.pcf.api.model.JobModel

So then I went and implemented another method in ApiResponse:

public static <T> ApiResponse<T> fromJson(String json, TypeToken<ApiResponse<T>> token)
{
    return new Gson().fromJson(json, token.getType());
}

And this time call it using function above:

ApiResponse<JobModel> response = ApiResponse.fromJson(new String(bytes), new TypeToken<ApiResponse<JobModel>>() {});

It seems to work fine.

I just can't get my head around this as two functions do exactly same thing. The only difference is that in first it purely relies on Java's generics where in second one I pass TypeToken as a parameter.

Can anyone explain me why is that happening and is there any way to fix it ?

War es hilfreich?

Lösung

A TypeToken is kind of a hack with generics. It depends on subclassing the type, either with an anonymous or normal class, and using Class#getGenericSuperclass() which states

If the superclass is a parameterized type, the Type object returned must accurately reflect the actual type parameters used in the source code.

In other words, in an anonymous class declaration like this

new TypeToken<ApiResponse<T>>() {}.getType())

the superclass is TypeToken<ApiResponse<T>>. It's equivalent to

class Subclass extends TypeToken<ApiResponse<T>>

assuming T was in scope. So when you call Class#getGenericSuperclass(), it will return a ParameterizedType that knows about ApiReponse<T> since that is the actual type parameters used in the source code.

When you call your original function with any of

ApiResponse<List<JobModel>> response = ApiResponse.fromJson(new String(bytes));
ApiResponse<Double> response = ApiResponse.fromJson(new String(bytes));
ApiResponse<JobModel> response = ApiResponse.fromJson(new String(bytes));

although the compiler will infer and bind the corresponding type as a type argument to the method invocation, the internals of the method will pass the same TypeToken object with ApiResponse<T>. Since Gson doesn't know what T is, it will use a default that depends on what it sees in the JSON. If it sees an object, it will use a LinkedTreeMap. If it sees a numeric primitive, it will use the double. Etc.

In the case where you pass a TypeToken,

ApiResponse.fromJson(new String(bytes), new TypeToken<ApiResponse<JobModel>>() {});

it's equivalent to

class Subclass extends TypeToken<ApiResponse<JobModel>>

In other words, Class#getGenericSuperclass() will return a ParameterizedType that has ApiResponse<JobModel>. Gson can extract the JobModel and use it as a hint for deserializing the JSON.

Can anyone explain me why is that happening and is there any way to fix it ?

There's nothing really to fix. That's just how it works.

Additional reading:

Andere Tipps

Generics work at compile-time,due to lack of reified Generics in Java (it isn't possible to do a T t = new T()), Gson itself is forced to use the TypeToken approach, as you see. Otherwise Gson would have done it in a much more elegant manner.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top