Amin Sh
Amin Sh

Reputation: 2814

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....}

Upvotes: 99

Views: 140145

Answers (14)

agitrubard
agitrubard

Reputation: 193

My Solution;

JsonUtil.class

package dev.agitrubard.util;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import lombok.experimental.UtilityClass;

import java.util.Arrays;
import java.util.List;

@UtilityClass
public class JsonUtil {

    private static final Gson GSON = new GsonBuilder().create();

    public static <T> List<T> fromJsonArray(String jsonArray, Class<T[]> clazz) {
        return Arrays.asList(
                GSON.fromJson(jsonArray, clazz)
        );
    }

}

Main.class

public class Main {

    public static void main(String[] args) {
        String json = "[{\"id\":1,\"firstName\":\"John\",\"lastName\":\"Doe\"},{\"id\":2,\"firstName\":\"Jane\",\"lastName\":\"Doe\"}]";
        List<User> users = JsonUtil.fromJsonArray(json, User[].class);
        for (User user : users) {
            System.out.println(user.getId() + " - " + user.getFirstName() + " " + user.getLastName());
        }
    }

    @Getter
    public static class User {
        private Long id;
        private String firstName;
        private String lastName;
    }

}

Console Output

1 - John Doe
2 - Jane Doe

Upvotes: 0

sarjeet singh
sarjeet singh

Reputation: 541

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);

Upvotes: 0

Sotirios Delimanolis
Sotirios Delimanolis

Reputation: 280122

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();
}

Upvotes: 56

Bharat Lalwani
Bharat Lalwani

Reputation: 1530

This is straightforward & simple in Kotlin.

val typeDataModelClass = Array<DataModelClass>::class.java

Upvotes: 1

Aslm Monir
Aslm Monir

Reputation: 146

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>>()!!)

Upvotes: 0

user15165947
user15165947

Reputation: 31

in kotlin you can

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

Upvotes: 3

Ibrahim Muhamad
Ibrahim Muhamad

Reputation: 351

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 = "[email protected]") 
 val jsonObjectAsString = model.json()
//output : {"name" : "example", "email" : "[email protected]"}

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" : "[email protected]"}"
 val model : Foo? = jsonObjectAsString.fromJson() 
 //or 
 val model = jsonObjectAsString.fromJson<Foo>() 

Upvotes: 9

TheLogicGuy
TheLogicGuy

Reputation: 690

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());
    }

Upvotes: 1

oldergod
oldergod

Reputation: 15010

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()

Upvotes: 92

shmosel
shmosel

Reputation: 50726

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();
}

Upvotes: 7

varren
varren

Reputation: 14731

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

Upvotes: 5

Ovidiu Latcu
Ovidiu Latcu

Reputation: 72331

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;

Upvotes: 2

MateSzvoboda
MateSzvoboda

Reputation: 331

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);

Upvotes: 33

Rodrigo Tavares
Rodrigo Tavares

Reputation: 369

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);
    }
}

Upvotes: 8

Related Questions