java - 为什么 Gson 序列化的是列表中的运行时类型,而不是指定的编译时类型?

标签 java json generics types gson

为什么Gson在序列化的时候好像忽略了嵌套的泛型类型声明?

我试图让 Gson 使用我指定的编译时类型,而不是列表中对象的运行时类型。我也在为 A.java 使用抽象父类(super class),但下面的示例存在同样的问题。

public class A {
    public String foo;
}

public class B extends A {
    public String bar;
}

public static void main( String[] args ) {
    Gson gson = new Gson();

    B b = new B();
    b.foo = "foo";
    b.bar = "bar";

    List<A> list = new ArrayList<A>();
    list.add(b);

    System.out.println(gson.toJson(b, new TypeToken<A>(){}.getType()));
    System.out.println(gson.toJson(b, new TypeToken<B>(){}.getType()));

    System.out.println(gson.toJson(list, new TypeToken<List<A>>(){}.getType()));
    System.out.println(gson.toJson(list, new TypeToken<List<B>>(){}.getType()));
}

输出:

{"foo":"foo"}
{"bar":"bar","foo":"foo"}
[{"bar":"bar","foo":"foo"}]
[{"bar":"bar","foo":"foo"}]

预期:

{"foo":"foo"}
{"bar":"bar","foo":"foo"}
[{"foo":"foo"}]
[{"bar":"bar","foo":"foo"}]

最佳答案

这是因为 Gson 默认序列化集合的方式。

为什么会这样?

如果您不关心原因而只想修复,请滚动到底部。

Gson的默认CollectionTypeAdapterFactory将其元素类型适配器包装在称为 TypeAdapterRuntimeTypeWrapper 的东西中.选择合适的适配器时,它使用 the following priorities :

// Order of preference for choosing type adapters
// First preference: a type adapter registered for the runtime type
// Second preference: a type adapter registered for the declared type
// Third preference: reflective type adapter for the runtime type (if it is a sub class of the declared type)
// Fourth preference: reflective type adapter for the declared type

在这种情况下,第三个偏好是 B 的适配器,第四个偏好是 A 的适配器。使用默认序列化器时这是不可避免的,因为有 no conditional in the CollectionTypeAdapterFactory。 :

public Adapter(Gson context, Type elementType,
    TypeAdapter<E> elementTypeAdapter,
    ObjectConstructor<? extends Collection<E>> constructor) {
  this.elementTypeAdapter =
      new TypeAdapterRuntimeTypeWrapper<E>(context, elementTypeAdapter, elementType);
  this.constructor = constructor;
}

当不使用 CollectionTypeAdapterFactory 时,此包装器不存在,这就是为什么它不会在您的前两个示例中发生。

tl;dr 好的,那我该如何解决呢?

解决此问题的唯一方法是注册自定义序列化程序。A 编写一个就可以了,在您的用例中:

public class ATypeAdapter extends TypeAdapter<A> {
  public A read(JsonReader reader) throws IOException {
    if (reader.peek() == JsonToken.NULL) {
      reader.nextNull();
      return null;
    }
    reader.beginObject();
    String name = reader.nextName();
    if(!"foo".equals(name)) throw new JsonSyntaxException("Expected field named foo");
    A a = new A();
    a.foo = reader.nextString();
    reader.endObject();
    return a;
  }

  public void write(JsonWriter writer, A value) throws IOException {
    if (value == null) {
      writer.nullValue();
      return;
    }
    writer.beginObject();
    writer.name("foo");
    writer.value(value.foo);
    writer.endObject();
  }
}

然后,如果你这样做:

public static void main( String[] args ) {
    GsonBuilder builder = new GsonBuilder();
    builder.registerTypeAdapter(new TypeToken<A>(){}.getType(), new ATypeAdapter());
    Gson gson = builder.create();

    B b = new B();
    b.foo = "foo";
    b.bar = "bar";

    List<A> list = new ArrayList<A>();
    list.add(b);

    System.out.println(gson.toJson(b, new TypeToken<A>(){}.getType()));
    System.out.println(gson.toJson(b, new TypeToken<B>(){}.getType()));

    System.out.println(gson.toJson(list, new TypeToken<List<A>>(){}.getType()));
    System.out.println(gson.toJson(list, new TypeToken<List<B>>(){}.getType()));
}

你得到了预期的输出:

{"foo":"foo"}
{"bar":"bar","foo":"foo"}
[{"foo":"foo"}]
[{"bar":"bar","foo":"foo"}]

关于java - 为什么 Gson 序列化的是列表中的运行时类型,而不是指定的编译时类型?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31757041/

相关文章:

java - List<Dog> 是 List<Animal> 的子类吗?为什么 Java 泛型不是隐式多态的?

java - 无法重现类型删除示例的结果

java - 如何使用 Maven 包含远程 java 源?

java.sql.SQLException : Io exception: The Network Adapter could not establish the connection

php - json_encode() 是否可以防止 SQL 注入(inject)?

json - 如何在 Angular 6 中过滤复杂结构化的 Json 数据

php - json 解析失败 - cocoa 错误 3840 和字符 0 周围的无效值

java - c 的远程方法调用

java - 计算文档字段中正则表达式查询匹配的数量

java - List<Dog> 是 List<Animal> 的子类吗?为什么 Java 泛型不是隐式多态的?