7

I want to write a generic function which deserialise a generic type List with Gson here is the code:

private <T> List<T> GetListFromFile(String filename)
    {
        //Read textfile
        BufferedReader reader;
        String data="";
        try 
        {
            reader = new BufferedReader(new FileReader(filename));
            data = reader.readLine();
            reader.close();
        } 
        catch (FileNotFoundException ex) 
        {

        } 
        catch (IOException ex) 
        {

        }
        if (data == null) 
        {
            List<T> Spiel = new ArrayList<T>();
            return Spiel;
        }
        else
        {
            //get list with Deserialise
            Gson gson = new Gson();
            List<T> something = gson.fromJson(data, new TypeToken<List<T>>(){}.getType());
            return something;
        }
    }

But this code is not working, i get a strange structure but not a List of my type

When i'am using:

List<concreteType> something = gson.fromJson(data, new TypeToken<List<T>>(){}.getType());

i works i get a List<concreteType>!!

But i need a generic function, how can i fix it?

Regards rubiktubik

SLaks
  • 800,742
  • 167
  • 1,811
  • 1,896
rubiktubik
  • 711
  • 9
  • 25

2 Answers2

9

The approach of use TypeToken won't work.

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.

So, you have two options:

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

private static class ListParameterizedType implements ParameterizedType {

    private Type type;

    public 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: Parse the list manually and then for each element use Gson

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);
    }
}
Martin Forte
  • 541
  • 10
  • 14
6

There is no way to do it without passing actual type of T (as Class<T>) to your method.

But if you pass it explicitly, you can create a TypeToken for List<T> as follows:

private <T> List<T> GetListFromFile(String filename, Class<T> elementType) {
    ...
    TypeToken<ArrayList<T>> token = new TypeToken<ArrayList<T>>() {};
    List<T> something = gson.fromJson(data, token.getType());
    ...
}

See also:

EpicPandaForce
  • 71,034
  • 25
  • 221
  • 371
axtavt
  • 228,184
  • 37
  • 489
  • 472
  • 2
    How come you have passed `com.google.common.reflect.TypeToken` to `com.google.gson.fromJson()` ? – tfranckiewicz Aug 21 '14 at 20:32
  • @tfranckiewicz `TypeToken() {}.getType()` is a convenience idiom to construct a `Type` object which represents a complete generic type. In this case, `List`, for an arbitrary, specific `T`. Passing `List.class` to `fromJson` does not work, because it doesn't capture the type of the list's elements. [Documentation here.](https://github.com/google/gson/blob/master/UserGuide.md#serializing-and-deserializing-generic-types) – jpaugh May 05 '16 at 19:28
  • 1
    This doesn't work. `T` is erased, so `gson` does not actually receive the type information. It will just use a default type as element type for `ArrayList` to parse. – Jorn Vernee May 30 '17 at 18:59