22

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

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
christophmccann
  • 4,181
  • 7
  • 42
  • 66

3 Answers3

14

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.

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • 1
    I was detaching the actual use of GSON from the mechanism that uses it. I have an ObjectDecoder interface with method decode(String json). This means I can switch between different implementations e.g. GSON, Jackson etc. I then use Guice to inject the dependency in to the mechanism. – christophmccann Nov 19 '10 at 17:03
  • 3
    Your best bet is really to reuse Gson's `TypeToken` as class or method argument or at least reinvent it if you don't want a 3rd party dependency being exposed in your API. [It's open source by Apache license](http://code.google.com/p/google-gson/source/browse/trunk/src/main/java/com/google/gson/reflect/TypeToken.java?spec=svn2&r=2). [Background explanation here](http://sites.google.com/site/gson/gson-user-guide#TOC-Serializing-and-Deserializing-Gener). – BalusC Nov 19 '10 at 17:04
5

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.

StaxMan
  • 113,358
  • 34
  • 211
  • 239
  • 4
    This won't work for parameterized classes where this question is all about. There's no such thing like `Map.class`, only `Map.class`. See also the "background explanation" link in the comment in my answer. – BalusC Jan 04 '11 at 18:45
  • Yes, I am fully aware of this issue, it's just that question did not suggest it was a parameterized class so simple answer seemed sufficient. It is a pity that JDK does not have equivalent of type token (type reference etc; each lib/framework having to define its own). – StaxMan Jan 04 '11 at 18:49
0

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.