使用动态ArrayList项类型的Gson TypeToken

96

我有这段代码:

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

它将一个JSON字符串转换为对象的List。 但现在我希望拥有一个动态类型(不仅仅是myClass)的ArrayList,可以在运行时定义。

ArrayList的元素类型将通过反射定义。

我尝试了这个:

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

但它不起作用。这是异常情况:

java.sql.SQLException: Fail to convert to internal representation: {....my json....}

1
那是一个SQLException。它与您发布的代码无关。请展示JDBC代码。 - Sotirios Delimanolis
1
@SotiriosDelimanolis 不是这样的!我只是想让TypeToken<ArrayList<T>>()代码接受动态Arraylist类型。就像这样:TypeToken<ArrayList<Class.forName(MyClass)>> - Amin Sh
13个回答

90

55
您提出的语法无效。以下是:
new TypeToken<ArrayList<Class.forName(MyClass)>>

由于您试图传递方法调用而期望类型名称,因此无效。
以下内容
new TypeToken<ArrayList<T>>() 

由于泛型(类型擦除)和反射的工作方式,提取编译时类型变量的值不可能。整个“TypeToken”技巧之所以可行是因为“Class#getGenericSuperclass()”执行以下操作:
返回表示此类(接口、原始类型或void)直接超类的类型。 如果超类是一个参数化类型,则返回的Type对象必须准确地反映源代码中使用的实际类型参数。
换句话说,如果它看到 “ArrayList ”,那就是它将返回的ParameterizedType,您将无法提取类型变量“T”在编译时的值。
“Type”和“ParameterizedType”都是接口。 您可以提供自己实现的实例 (定义一个实现任一接口并覆盖其方法的类),或使用其最新版本中提供的“TypeToken”中的 有用的工厂方法之一。 例如:
private Type setModelAndGetCorrespondingList2(Class<?> typeArgument) {
    return TypeToken.getParameterized(ArrayList.class, typeArgument).getType();
}

我认为可以使用这里展示的技术:https://dev59.com/mGYq5IYBdhLWcg3w2UMU - Benoit Duffez
2
@BenoitDuffez 小心。你链接的答案做了不同的事情。它使用了一个实际的Class对象,所以Gson可以很好地知道要反序列化成什么类型。在OP的问题中,他们想要在不使用Class对象的情况下,只通过类型参数来实现,而这个类型参数在方法本身中是不可用的。 - Sotirios Delimanolis
@SotiriosDelimanolis:你说得完全正确。我认为将类放在这里是一个可以接受的代价,让它能够工作。但实际上,链接的答案与OP所要求的不同。感谢您的澄清。 - Benoit Duffez

32

选项 1 - 自己实现 java.lang.reflect.ParameterizedType 并将其传递给 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)
}

然后简单地:
Type type = new ListParameterizedType(clazz);
List<T> list = gson.fromJson(json, type);

请注意,根据javadoc,应该也要实现equals方法。 选项2 -(不建议使用)重用gson内部...
这也可以工作,至少在Gson 2.2.4中。
Type type = com.google.gson.internal.$Gson$Types.newParameterizedTypeWithOwner(null, ArrayList.class, clazz);

9

使用Kotlin,您可以使用以下函数将任何(JsonArray / JsonObject)转换(从/到),而无需发送TypeToken,只需一行即可完成:

将任何类或数组转换为JSON字符串

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

使用示例:

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

将JSON字符串转换为任何类或数组

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

使用示例:

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

8
这对我很有用:
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);
    }
}

您的代码在方法中使用了“public <T>”,这是使用Java泛型方法实现的,文档链接:https://docs.oracle.com/javase/tutorial/extra/generics/methods.html。 - ʕ ᵔᴥᵔ ʔ

7
你可以使用Guava更强大的 TypeToken 来实现这一点:
private static <T> Type setModelAndGetCorrespondingList2(Class<T> type) {
    return new TypeToken<ArrayList<T>>() {}
            .where(new TypeParameter<T>() {}, type)
            .getType();
}

5

sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl 可以使用,无需自定义实现

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

可与地图和任何其他集合一起使用:

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

以下是一个不错的演示,您可以了解如何在自定义反序列化程序中使用它:使用Gson反序列化ImmutableList


3

在 Kotlin 中,您可以:

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

2
您可以实现它。您只需要首先将数据解析为 JsonArray,然后逐个转换每个对象,并将其添加到 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;

循环内部的 try / catch 是非常低效的。 - dobrivoje
无论是在循环内部还是外部使用try/catch对性能没有影响。 - Carson Holzheimer

1
完整的工作解决方案:
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());
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接