使用Gson反序列化List<T>对象?

531
我希望通过Google Gson传输一个列表对象,但我不知道如何反序列化泛型类型。
在查看this(BalusC的答案)后,我尝试了以下方法:
MyClass mc = new Gson().fromJson(result, new List<MyClass>() {}.getClass());

然后在Eclipse中,我收到了一个错误提示:"类型new List<MyClass>() {}必须实现继承的抽象方法..."如果我使用快速修复,我会得到超过20个方法桩的怪物。

我相信一定有更简单的解决方案,但是我似乎找不到!

现在我有这个:

Type listType = new TypeToken<List<MyClass>>() {}.getType();

MyClass mc = new Gson().fromJson(result, listType);

然而,在fromJson这一行,我遇到了以下异常:

java.lang.NullPointerException
at org.apache.harmony.luni.lang.reflect.ListOfTypes.length(ListOfTypes.java:47)
at org.apache.harmony.luni.lang.reflect.ImplForType.toString(ImplForType.java:83)
at java.lang.StringBuilder.append(StringBuilder.java:203)
at com.google.gson.JsonDeserializerExceptionWrapper.deserialize(JsonDeserializerExceptionWrapper.java:56)
at com.google.gson.JsonDeserializationVisitor.invokeCustomDeserializer(JsonDeserializationVisitor.java:88)
at com.google.gson.JsonDeserializationVisitor.visitUsingCustomHandler(JsonDeserializationVisitor.java:76)
at com.google.gson.ObjectNavigator.accept(ObjectNavigator.java:106)
at com.google.gson.JsonDeserializationContextDefault.fromJsonArray(JsonDeserializationContextDefault.java:64)
at com.google.gson.JsonDeserializationContextDefault.deserialize(JsonDeserializationContextDefault.java:49)
at com.google.gson.Gson.fromJson(Gson.java:568)
at com.google.gson.Gson.fromJson(Gson.java:515)
at com.google.gson.Gson.fromJson(Gson.java:484)
at com.google.gson.Gson.fromJson(Gson.java:434)

我捕获了JsonParseExceptions,并且result不为null。

我使用调试器检查了listType,得到以下结果:

  • list Type
    • args = ListOfTypes
      • list = null
      • resolvedTypes = Type[ 1 ]
    • loader = PathClassLoader
    • ownerType0 = null
    • ownerTypeRes = null
    • rawType = Class (java.util.ArrayList)
    • rawTypeName = "java.util.ArrayList"

所以看起来getClass调用没有正常工作。有什么建议吗?

我在Gson用户指南上进行了检查。它提到了一个运行时异常,在将通用类型解析为Json时应该发生。我按照示例“错误”地执行了此操作(未在上面显示),但根本没有出现该异常。因此,我按照用户指南建议的更改了序列化。但是这并没有帮助。

编辑:

问题已解决,请参见我的下面的回答。


1
你指向的答案使用了 TokenType。你尝试过那种方式吗? - Nishant
刚刚得到了相同的提示作为答案。下次我会更仔细地看例子的。;) - jellyfish
你能否尝试在类型标记中实现列表?由于你的原始类型是数组列表,因此应该尝试使用数组列表。 - uncaught_exceptions
15个回答

1103

反序列化泛型集合的方法:

import java.lang.reflect.Type;
import com.google.gson.reflect.TypeToken;

...

Type listType = new TypeToken<ArrayList<YourClass>>(){}.getType();
List<YourClass> yourClassList = new Gson().fromJson(jsonArray, listType);

由于评论中有多人提到,这里解释一下TypeToken类的使用方式。构造函数new TypeToken<...>() {}.getType()将编译时类型(在<>之间)捕获到运行时的java.lang.reflect.Type对象中。与只能表示原始类型(已擦除)的Class对象不同,Type对象可以表示Java语言中的任何类型,包括泛型类型的参数化实例。 TypeToken类本身没有公共构造函数,因为你不应该直接构造它。相反,你总是构造一个匿名子类(因此需要{},这是表达式的必要部分)。
由于类型擦除,TypeToken类只能捕获在编译时完全已知的类型。(也就是说,你不能对类型参数T执行new TypeToken<List<T>>() {}.getType()。)
更多信息,请参见TypeToken类的文档

34
在新版本的GSON中,TypeToken构造函数不是公共的,因此您会收到构造函数不可见的错误。在这种情况下,您需要怎么做? - Pablo
8
使用最新版本的GSON(2.2.4),它可以正常工作。你可以在这里访问构造函数。 - user1563700
9
需要导入以下内容:import java.lang.reflect.Type; import com.google.gson.reflect.TypeToken - Umair Saleem
5
如果 YourClass 在代码中是固定的,那么这很好。但如果类在运行时生成怎么办? - jasxir
1
@jasxir,如果类型参数是动态的且在编译时不知道,请使用TypeToken.getParameterized,如此答案所示。 - Marcono1234
显示剩余8条评论

308

另一种方法是将数组作为类型使用,例如:

MyClass[] mcArray = gson.fromJson(jsonString, MyClass[].class);

这样你就避免了与Type对象相关的所有麻烦,如果你真的需要一个列表,你可以通过以下方式将数组转换为列表:

List<MyClass> mcList = Arrays.asList(mcArray);

在我看来,这更易读。

如果要创建一个可修改的实际列表(请参阅Arrays.asList()的限制),只需执行以下操作:

List<MyClass> mcList = new ArrayList<>(Arrays.asList(mcArray));

4
太棒了!我要如何使用它来进行反射? 我不知道"MyClass"的值,因为它将会动态定义! - Amin Sh
2
注意:请小心,mcList不是完整的列表。许多功能将无法正常工作。 - njzk2
4
如何在泛型中使用它?T[] yourClassList = gson.fromJson(message, T[].class); // 无法从类型变量中进行选择 - Pawel Cioch
3
在那个评论发表时,这个答案中的mcList仅是调用Arrays.asList方法的结果。该方法返回一个列表,其中大多数可选方法未被实现并会抛出异常。例如,您不能向该列表添加任何元素。正如后面的编辑所示,Arrays.asList存在一些限制,因此将其包装成实际的ArrayList可以让您获得一个在许多情况下更为有用的列表。 - njzk2
2
如果您需要在运行时构造任意元素类型的数组类型,可以使用Array.newInstance(clazz, 0).getClass(),如David Wood的答案所述。 - Daniel Pryden
显示剩余2条评论

88

自Gson 2.8版本以后,我们可以创建如下的实用函数:

public <T> List<T> getList(String jsonArray, Class<T> clazz) {
    Type typeOfT = TypeToken.getParameterized(List.class, clazz).getType();
    return new Gson().fromJson(jsonArray, typeOfT);
}

示例用法:

String jsonArray = ...
List<User> user = getList(jsonArray, User.class);

3
TypeToken#getParameterized这种方式看起来比使用匿名子类的hack要好得多。 - Nikolay Kulachenko
1
我按照你的方法“原样”复制,但它不起作用:编译器显示“方法getParameterized(Class<List>, Class<T>)对于TypeToken类型未定义”。我检查了我的Gson版本(2.8.0)和文档,这边一切都没问题...最终我使用了@Happier的解决方案,它可以正常工作。 - leguminator
1
这应该是被接受的解决方案,简单易用,它使用了Gson API,并且没有任何变通方法。+1 - 4gus71n
2
就其价值而言,使用匿名的TypeToken子类解决方案并不是一种hack,而是Gson官方推荐的用法(请参阅其Javadoc和用户指南)。其主要优点在于确保您构造正确的参数化类型。TypeToken.getParameterized无法在编译时执行任何验证,因此您可以构造诸如List<String,String,String>Map<Integer>之类的类型;最新的Gson版本至少会在运行时检测到这些错误的类型。 - Marcono1234
1
@Marcono1234 匿名 TypeToken 的另一个优点在于我们需要嵌套泛型时。例如,当解析 [[1,2],[3,4],[5,6]] 时,我宁愿使用 new TypeToken<List<List<Integer>>(){}.getType() 来阅读/维护代码,而不是使用 TypeToken.getParameterized(List.class, TypeToken.getParameterized(List.class, Integer.class).getType()).getType() - Pshemo
显示剩余2条评论

32

请参考此帖子。 Java Type Generic as Argument for GSON

我有更好的解决方案。以下是列表的包装类,使得包装类可以存储准确的列表类型。

public class ListOfJson<T> implements ParameterizedType
{
  private Class<?> wrapped;

  public ListOfJson(Class<T> wrapper)
  {
    this.wrapped = wrapper;
  }

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

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

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

然后,代码可以很简单:

public static <T> List<T> toList(String json, Class<T> typeClass)
{
    return sGson.fromJson(json, new ListOfJson<T>(typeClass));
}

mEntity.rulePattern 是什么? - Al Lelopath
这只是一个测试用的样本对象,您不需要关心它。使用 toList 方法,一切都会顺利进行。 - Happier

29

嗯,还有一种达到相同结果的方法。我们使用它是因为它易读。

不用写这种难以阅读的句子:

Type listType = new TypeToken<ArrayList<YourClass>>(){}.getType();
List<YourClass> list = new Gson().fromJson(jsonArray, listType);
创建一个继承你的对象列表的空类:
public class YourClassList extends ArrayList<YourClass> {}

在解析JSON时使用它:

List<YourClass> list = new Gson().fromJson(jsonArray, YourClassList.class);

13

对于 Kotlin,简单来说:

import java.lang.reflect.Type
import com.google.gson.reflect.TypeToken
...
val type = object : TypeToken<List<T>>() {}.type

或者,这里有一个有用的函数:

fun <T> typeOfList(): Type {
    return object : TypeToken<List<T>>() {}.type
}

然后,使用:

val type = typeOfList<YourMagicObject>()

我使用了你的代码,利用具体化类型创建了这个函数: inline fun <reified T> buildType() = object : TypeToken<T>() {}.type!! 并且使用List类型进行了调用:buildType<List<YourMagicObject>>(). - coffeemakr
@coffeemakr 这里不需要具体化类型。 - Chad Bingham
哦。但是为什么你在buildType中创建了一个ArrayList的类型标记,并且还使用了泛型类型调用函数呢?这是打字错误吗?- 这将创建ArrayList <ArrayList <YourMagicObject>>。 - coffeemakr
@coffeemakr 啊,是的。打错字了。 - Chad Bingham

7
public static final <T> List<T> getList(final Class<T[]> clazz, final String json)
{
    final T[] jsonToObject = new Gson().fromJson(json, clazz);

    return Arrays.asList(jsonToObject);
}

例子:

getList(MyClass[].class, "[{...}]");

不错。但是这个答案与DevNG的上面的回答重复了,而且是在2年前写的:https://dev59.com/82035IYBdhLWcg3wNtNw#17300003(阅读该答案以了解此方法的注意事项) - Lambart

6

以下是一种适用于动态定义类型的解决方案。诀窍是使用Array.newInstance()来创建正确类型的数组。

public static <T> List<T> fromJsonList(String json, Class<T> clazz) {
    Object [] array = (Object[])java.lang.reflect.Array.newInstance(clazz, 0);
    array = gson.fromJson(json, array.getClass());
    List<T> list = new ArrayList<T>();
    for (int i=0 ; i<array.length ; i++)
        list.add(clazz.cast(array[i]));
    return list; 
}

6

由于它回答了我的原始问题,我接受了doc_180的答案,但如果有人再次遇到这个问题,我也将回答我的问题的后半部分:

我描述的NullPointerError与列表本身无关,而是与其内容有关!

"MyClass"类没有“无参”构造函数,其超类也没有。一旦我在MyClass和其超类中添加了一个简单的“MyClass()”构造函数,所有东西都正常工作了,包括doc_180建议的列表序列化和反序列化。


1
如果您有一个抽象类列表,您将会得到相同的错误。我猜这是GSON的“无法实例化类”的通用错误消息。 - Drew
添加构造函数的提示帮助我意识到为什么我所有的值都是 null。我的 JSON 字符串中有类似 "To" 和 "From" 的字段名称,但是在我的对象中对应的字段是小写的 "to" 和 "from",所以它们被跳过了。 - Rune

3
我想再提供一种可能性。如果你不想使用TypeToken,而是想将JSON对象数组转换为ArrayList,则可以按照以下步骤进行:
如果您的JSON结构如下:
{

"results": [
    {
        "a": 100,
        "b": "value1",
        "c": true
    },
    {
        "a": 200,
        "b": "value2",
        "c": false
    },
    {
        "a": 300,
        "b": "value3",
        "c": true
    }
]

}

and your class structure is like:

public class ClassName implements Parcelable {

    public ArrayList<InnerClassName> results = new ArrayList<InnerClassName>();
    public static class InnerClassName {
        int a;
        String b;
        boolean c;      
    }
}

然后你可以像这样解析它:
Gson gson = new Gson();
final ClassName className = gson.fromJson(data, ClassName.class);
int currentTotal = className.results.size();

现在你可以访问 className 对象的每个元素。

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