Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Gson TypeToken with dynamic ArrayList item type

I have this code:

Type typeOfObjectsList = new TypeToken<ArrayList<myClass>>() {}.getType();
List<myClass> objectsList = new Gson().fromJson(json, typeOfObjectsList);

It converts a JSON string to a List of objects. But now I want to have this ArrayList with a dynamic type (not just myClass), defined at runtime.

The ArrayList's item type will be defined with reflection.

I tried this:

    private <T> Type setModelAndGetCorrespondingList2(Class<T> type) {
        Type typeOfObjectsListNew = new TypeToken<ArrayList<T>>() {}.getType();
        return typeOfObjectsListNew;
    }

But it doesn't work. This is the exception:

java.sql.SQLException: Fail to convert to internal representation: {....my json....}
like image 922
Amin Sh Avatar asked Dec 25 '13 14:12

Amin Sh


4 Answers

Since Gson 2.8.0, you can use TypeToken#getParameterized(Type rawType, Type... typeArguments) to create the TypeToken, then getType() should do the trick.

For example:

TypeToken.getParameterized(ArrayList.class, myClass).getType()
like image 155
oldergod Avatar answered Nov 09 '22 22:11

oldergod


The syntax you are proposing is invalid. The following

new TypeToken<ArrayList<Class.forName(MyClass)>>

is invalid because you're trying to pass a method invocation where a type name is expected.

The following

new TypeToken<ArrayList<T>>() 

is not possible because of how generics (type erasure) and reflection works. The whole TypeToken hack works because Class#getGenericSuperclass() does the following

Returns the Type representing the direct superclass of the entity (class, interface, primitive type or void) represented by this Class.

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, if it sees ArrayList<T>, that's the ParameterizedType it will return and you won't be able to extract the compile time value that the type variable T would have had.

Type and ParameterizedType are both interfaces. You can provide an instance of your own implementation (define a class that implements either interface and overrides its methods) or use one of the helpful factory methods that TypeToken provides in its latest versions. For example,

private Type setModelAndGetCorrespondingList2(Class<?> typeArgument) {
    return TypeToken.getParameterized(ArrayList.class, typeArgument).getType();
}
like image 33
Sotirios Delimanolis Avatar answered Nov 09 '22 21:11

Sotirios Delimanolis


Option 1 - implement java.lang.reflect.ParameterizedType yourself and pass it to Gson.

private static class ListParameterizedType implements ParameterizedType {

    private Type type;

    private ListParameterizedType(Type type) {
        this.type = type;
    }

    @Override
    public Type[] getActualTypeArguments() {
        return new Type[] {type};
    }

    @Override
    public Type getRawType() {
        return ArrayList.class;
    }

    @Override
    public Type getOwnerType() {
        return null;
    }

    // implement equals method too! (as per javadoc)
}

Then simply:

Type type = new ListParameterizedType(clazz);
List<T> list = gson.fromJson(json, type);

Note that as per javadoc, equals method should also be implemented.

Option 2 - (don't do this) reuse gson internal...

This will work too, at least with Gson 2.2.4.

Type type = com.google.gson.internal.$Gson$Types.newParameterizedTypeWithOwner(null, ArrayList.class, clazz);
like image 32
MateSzvoboda Avatar answered Nov 09 '22 21:11

MateSzvoboda


This worked for me:

public <T> List<T> listEntity(Class<T> clazz)
        throws WsIntegracaoException {
    try {
        // Consuming remote method
        String strJson = getService().listEntity(clazz.getName());

        JsonParser parser = new JsonParser();
        JsonArray array = parser.parse(strJson).getAsJsonArray();

        List<T> lst =  new ArrayList<T>();
        for(final JsonElement json: array){
            T entity = GSON.fromJson(json, clazz);
            lst.add(entity);
        }

        return lst;

    } catch (Exception e) {
        throw new WsIntegracaoException(
                "WS method error [listEntity()]", e);
    }
}
like image 8
Rodrigo Tavares Avatar answered Nov 09 '22 23:11

Rodrigo Tavares