Using generics with GSON

2019-01-11 13:04发布

问题:

I am using GSON to decode JSON into an object of type T e.g.

public T decode(String json) {
    Gson gson = new Gson();
    return gson.fromJson(json, new TypeToken<T>() {}.getType());
}

This however returns an exception -

java.lang.AssertionError: Unexpected type. Expected one of: java.lang.reflect.ParameterizedType, java.lang.reflect.GenericArrayType, but got: sun.reflect.generics.reflectiveObjects.TypeVariableImpl, for type token: T

I thought that by using TypeToken I avoided Type Erasure.

Am I wrong?

Thanks

回答1:

First of all, I fail to see how it's useful to wrap Gson like that.

As to your problem, the information about generic type T itself is not available during runtime. It's been erased. It's only available during compile time. You want to parameterize it with the actual type instead like new TypeToken<List<String>>.

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.

In order to be able to pass the actual type around, you've to reinvent the same thing as TypeToken is already doing. And this makes no sense :) Just reuse it or just use Gson straight without wrapping it in some helper class like that.



回答2:

I think the first answer is not pointing out the actual solution: you MUST also pass Class instance along with T, like so:

public T decode(String json, Class<T> cls) {
    Gson gson = new Gson();
    return gson.fromJson(json, cls);
}

This is because 'T' here is a type VARIABLE, not a type reference; and only used by compiler to add implicit casts and verify type compatibility. But if you pass actual class it can be used; and compiler will check type compatibility to reduce chance of mismatch.

Alternatively you could take in TypeToken and pass it; but TypeToken must be constructed with real type, not a type variable; type variable is of little use here. But if you do want to wrap things you wouldn't want caller to use TypeToken (which is a Gson type).

Same wrapping mechanism would work with other libs like Jackson, which you mentioned.



回答3:

My solution to this was to use the json parser and break it into pieces

public static <TT> PushObj<TT> fromJSON(String json, Class<TT> classType)
{
    JsonObject jObj = new JsonParser().parse(json).getAsJsonObject();
    String type = jObj.get("type").getAsString();
    JsonObject job = jObj.get("object").getAsJsonObject();
    TT obj = new Gson().fromJson(job, classType);
    return new PushObj<TT>(type, obj);
}

Where the object structure is: {String:Type, Generic:Object}

And the variables are: jObj is the JSONObject of the string passed in and job is the JSONObject of the generic object

So i used the json parser to get the type separately, and reflection for the object.