使用GSON反序列化嵌套泛型类时的奇怪行为 [英] Strange behavior when deserializing nested, generic classes with GSON

查看:323
本文介绍了使用GSON反序列化嵌套泛型类时的奇怪行为的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在写一个类,它将连接到服务器并基于一些参数,检索一个json字符串,该字符串将使用GSON解析到指定的(通过泛型)类。



负责课程的精简版本如下所示:

  class Executor< T> {

私人回应< T>响应;
$ b $ public void execute(){
Type responseType = new TypeToken< Response< T>>(){} .getType();
this.response = new Gson()。fromJson(json,responseType);
}

public Response< T> getResponse(){return this.response; }

$ b

JSON -variable 看起来像这样。)



存储数据一旦反序列化看起来像这样:

  class Response< T> {

私人列表< T> data = null;

public List< T> getData(){return this.data; }


$ / code>

数据试图去掉的类序列化为:

  public class Language {
public String alias;
公共字符串标签;
}

运行的代码使用了上面的类:

 执行者<语言> executor = new Executor< Language(); 
列表<语言> languages = executor.execute()。getResponse()。getData();
System.out.println(languages.get(0).alias); //异常发生在这里

其中导致以下异常


ClassCastException:com.google.gson.internal.StringMap无法转换为sunnerberg.skolbibliotek.book.Language

任何帮助或建议都非常感谢!

解决方案

简短的回答是,您需要移动从 Executor 中创建 TypeToken ,绑定 T 当您创建令牌( new TypeToken< Response< Language>>(){} 时,< Response< T> Executor 构造函数。



长答案是:



类型上的泛型通常在运行时被擦除,除非类型是编译的且泛型参数是绑定的。在这种情况下,编译器会将通用类型信息插入到编译后的类中。在其他情况下,这是不可能的。



例如,考虑:

 列表与LT;整数> foo = new ArrayList< Integer>(); 

类IntegerList扩展ArrayList<整数> {...}
List< Integer> bar = new IntegerList();

在运行时,Java 知道 bar code>包含整数,因为在编译时 Integer 类型绑定到 ArrayList ,所以泛型类型信息保存在 IntegerList 类文件中。但是, foo 的通用类型信息已被删除,因此在运行时不可能确定 foo 是应该包含整数 s。



因此,经常会出现我们需要泛型类型信息通常会在运行前擦除,例如在GSON中解析JSON数据的情况。在这些情况下,我们可以利用这样的事实,即在编译时绑定类型信息(如上面的 IntegerList 示例),通过使用类型标记,它们实际上只是小型的匿名类,可以方便地存储泛型类型信息。



现在到您的代码:

 键入responseType = new TypeToken< ; Response< T>>(){} .getType(); 

Executor 类的这一行中,我们创建一个类型为 Response< T> 的硬编码(绑定)的匿名类(继承自 TypeToken )编译时间。因此,在运行时,GSON能够确定您需要 Response< T> 的对象。但它不知道 T 是什么,因为你没有在编译时指定它!因此GSON无法确定它创建的 Response 对象的 List 中的类型,它只是创建一个 StringMap 代替。



故事的寓意是您需要指定 T 在编译时。如果 Executor 是用来泛泛使用的,那么您可能需要在客户端代码中在该类之外创建类型标记。例如:

  class Executor< T> {

private TypeToken< Response< T>> responseTyp的;
private Response< T>响应;

public Executor(TypeToken< Response<>> responseType){
this.responseType = responseType;


public void execute(){
this.response = new Gson()。fromJson(json,responseType.getType());
}

public Response< T> getResponse(){return this.response; }

}

//客户端代码:
执行程序<语言> executor = new Executor< Language>(new TypeToken< Response< Language>>(){});
executor.execute();
列表<语言> languages = executor.getResponse()。getData();
System.out.println(languages.get(0).alias); //打印be

顺便说一句,我在我的机器上测试了上述内容。 p>

很抱歉,如果这太长了!


I'm writing a class which will connect to a server and based on some arguments, retrieve a json-string which will be parsed with GSON to the specified (via generics) class.

A stripped down version of the class in charge looks like this:

class Executor<T> {

    private Response<T> response;

    public void execute() {
        Type responseType = new TypeToken<Response<T>>() {}.getType();
        this.response = new Gson().fromJson(json, responseType);
    }

    public Response<T> getResponse() { return this.response; }

}

(the JSON-variable looks like this.)

The class which stores the data once de-serialized looks like this:

class Response<T> {

    private List<T> data = null;

    public List<T> getData() { return this.data; }

}

The class which the data is trying to be de-serialized to:

public class Language {
    public String alias;
    public String label;
}

And the code which runs utilizes the classes above:

Executor<Language> executor = new Executor<Language();
List<Language> languages = executor.execute().getResponse().getData();
System.out.println(languages.get(0).alias); // exception occurs here

Which results in the following exception

ClassCastException: com.google.gson.internal.StringMap cannot be cast to sunnerberg.skolbibliotek.book.Language

Any help or suggestions are greatly appreciated!

解决方案

The short answer is that you need to move the creation of the TypeToken out of Executor, bind the T in Response<T> when you create the token (new TypeToken<Response<Language>>() {}), and pass in the type token to the Executor constructor.

The long answer is:

Generics on a type are typically erased at runtime, except when the type is compiled with the generic parameter bound. In that case, the compiler inserts the generic type information into the compiled class. In other cases, that is not possible.

So for instance, consider:

List<Integer> foo = new ArrayList<Integer>();

class IntegerList extends ArrayList<Integer> { ... }
List<Integer> bar = new IntegerList();

At runtime, Java knows bar contains integers because the Integer type is bound to ArrayList at compile time, so the generic type information is saved in the IntegerList class file. However, the generic type information for foo is erased, so at runtime it is not really possible to determine that foo is supposed to contain Integers.

So it often comes up that we need generic type information in a situation where it normally would be erased before runtime, such as here in the case of parsing JSON data in GSON. In these situations, we can take advantage of the fact that type information is preserved when it is bound at compile-time (as in the IntegerList example above) by using type tokens, which are really just tiny anonymous classes that conveniently store generic type information.

Now to your code:

Type responseType = new TypeToken<Response<T>>() {}.getType();

In this line of your Executor class, we create an anonymous class (inheriting from TypeToken) which has the type Response<T> hard coded (bound) at compile-time. So at runtime, GSON is able to determine that you want an object of Response<T>. But it doesn't know what T is, because you didn't specify it at compile-time! So GSON cannot determine what type will be in the List of the Response object it creates, and it just creates a StringMap instead.

The moral of the story is that you need to specify that T at compile-time. If Executor is meant to be used generically, you probably need to create the type token outside of that class, in your client code. Something like:

class Executor<T> {

    private TypeToken<Response<T>> responseType;
    private Response<T> response;

    public Executor(TypeToken<Response<T>> responseType) {
        this.responseType = responseType;
    }

    public void execute() {
        this.response = new Gson().fromJson(json, responseType.getType());
    }

    public Response<T> getResponse() { return this.response; }

}

// client code:
Executor<Language> executor = new Executor<Language>(new TypeToken<Response<Language>>() { });
executor.execute();
List<Language> languages = executor.getResponse().getData();
System.out.println(languages.get(0).alias); // prints "be"

By the way, I did test the above on my machine.

Sorry if that was too long!

这篇关于使用GSON反序列化嵌套泛型类时的奇怪行为的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆