95

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....}
Benoit Duffez
  • 11,839
  • 12
  • 77
  • 125
Amin Sh
  • 2,684
  • 2
  • 27
  • 42

13 Answers13

90

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()
shmosel
  • 49,289
  • 6
  • 73
  • 138
oldergod
  • 15,033
  • 7
  • 62
  • 88
54

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();
}
Sotirios Delimanolis
  • 274,122
  • 60
  • 696
  • 724
  • I think it's possible, using the technique shown here: http://stackoverflow.com/questions/14139437/java-type-generic-as-argument-for-gson – Benoit Duffez Apr 19 '14 at 15:55
  • 2
    @BenoitDuffez Careful. The answer you linked is doing something different. It's using an actual `Class` object, so Gson can very well know what type to deserialize to. In the OP's question, they want to do it without using the `Class` object and only through the type argument, which isn't available within the method itself. – Sotirios Delimanolis Apr 19 '14 at 16:09
  • @SotiriosDelimanolis: You're absolutely right. I thought that putting the class was an acceptable price to get this to work. But indeed, the linked answer does something different from what the OP asked for. Thanks for the clarification. – Benoit Duffez Apr 19 '14 at 16:10
32

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);
MateSzvoboda
  • 321
  • 3
  • 2
9

With kotlin you can use a below functions to converting (from/to) any (JsonArray/JsonObject) just in one line without need to send a TypeToken :-

Convert any class or array to JSON string

inline fun <reified T : Any> T?.json() = this?.let { Gson().toJson(this, T::class.java) }

Example to use :

 val list = listOf("1","2","3")
 val jsonArrayAsString = list.json() 
 //output : ["1","2","3"]

 val model= Foo(name = "example",email = "t@t.com") 
 val jsonObjectAsString = model.json()
//output : {"name" : "example", "email" : "t@t.com"}

Convert JSON string to any class or array

inline fun <reified T : Any> String?.fromJson(): T? = this?.let {
    val type = object : TypeToken<T>() {}.type
    Gson().fromJson(this, type)
}

Example to use :

 val jsonArrayAsString = "[\"1\",\"2\",\"3\"]"
 val list = jsonArrayAsString.fromJson<List<String>>()

 val jsonObjectAsString = "{ "name" : "example", "email" : "t@t.com"}"
 val model : Foo? = jsonObjectAsString.fromJson() 
 //or 
 val model = jsonObjectAsString.fromJson<Foo>() 
Ibrahim Muhamad
  • 331
  • 2
  • 8
8

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);
    }
}
Rodrigo Tavares
  • 369
  • 3
  • 14
  • Your code with ' public ' in the method works using Java generic methods, documented here: https://docs.oracle.com/javase/tutorial/extra/generics/methods.html – ʕ ᵔᴥᵔ ʔ Nov 01 '17 at 06:09
7

You can do this with Guava's more powerful TypeToken:

private static <T> Type setModelAndGetCorrespondingList2(Class<T> type) {
    return new TypeToken<ArrayList<T>>() {}
            .where(new TypeParameter<T>() {}, type)
            .getType();
}
shmosel
  • 49,289
  • 6
  • 73
  • 138
5

sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl workes. No need for custom implementation

Type type = ParameterizedTypeImpl.make(List.class, new Type[]{childrenClazz}, null);
List list = gson.fromJson(json, type);

Can be used with maps and any other collection:

ParameterizedTypeImpl.make(Map.class, new Type[]{String.class, childrenClazz}, null);

Here is nice demo how you can use it in custom deserializer: Deserializing ImmutableList using Gson

Community
  • 1
  • 1
varren
  • 14,551
  • 2
  • 41
  • 72
3

in kotlin you can

inline fun <reified T> parseData(row :String): T{
   return Gson().fromJson(row, object: TypeToken<T>(){}.type)
}
2

You can actually do it. You just need to parse first your data into an JsonArray and then transform each object individually, and add it to a List :

Class<T> dataType;

//...

JsonElement root = jsonParser.parse(json);
List<T> data = new ArrayList<>();
JsonArray rootArray = root.getAsJsonArray();
for (JsonElement json : rootArray) {
    try {
        data.add(gson.fromJson(json, dataType));
    } catch (Exception e) {
        e.printStackTrace();
    }
}
return data;
Ovidiu Latcu
  • 71,607
  • 15
  • 76
  • 84
1

Fully working solution:

String json = .... //example: mPrefs.getString("list", "");
ArrayList<YouClassName> myTypes = fromJSonList(json, YouClassName.class);


public <YouClassName> ArrayList<YouClassName> fromJSonList(String json, Class<YouClassName> type) {
        Gson gson = new Gson();
        return gson.fromJson(json, TypeToken.getParameterized(ArrayList.class, type).getType());
    }
TheLogicGuy
  • 682
  • 8
  • 19
0

Well actually i have made extension functions to resolve this , saving a list to SharedPrefrence and retrieve them in anyplace in the app like this :

use this to Save a List to SharedPref. For example :

fun <T> SaveList(key: String?, list: List<T>?) {
val gson = Gson()
val json: String = gson.toJson(list)
getSharedPrefrences(App.getAppContext() as Application).edit().putString(key, json).apply()}

return the list in any place from sharedPref. like this :

fun Context.getList(key: String): String? {
 return getSharedPrefrences(App.getAppContext() as Application).getString(key, null)}

inline fun <reified T : Any> String?.fromJson(): T? = this?.let {
val type = object : TypeToken<T>() {}.type
Gson().fromJson(this, type)}

Usage in Saving and Getting List from Saved one is like :

saveList(Consts.SLIDERS, it.data)  

SetSliderData(this.getList(Consts.SLIDERS).fromJson<MutableList<SliderResponseItem>>()!!)
Aslm Monir
  • 51
  • 3
0

This is straightforward & simple in Kotlin.

val typeDataModelClass = Array<DataModelClass>::class.java
Bharat Lalwani
  • 1,277
  • 15
  • 17
0

this code is json string data to convert in arraylist model

String json = getIntent().getExtras().getString("submitQuesList", "");
Type typeOfObjectsList = new TypeToken<ArrayList<Datum_ques>>() {}.getType();
submitQuesList = new Gson().fromJson(json, typeOfObjectsList);
bric3
  • 40,072
  • 9
  • 91
  • 111
sarjeet singh
  • 461
  • 4
  • 10