简体   繁体   English

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

[英]Why does Gson serializes runtime type in list, not specified compile-time type?

Why does it seem that Gson ignores the nested generic type declaration when serializing? 为什么序列化时Gson似乎忽略了嵌套的泛型类型声明?

I am trying to get Gson to use the compile-time type I specify, instead of the runtime type of objects in the list. 我试图让Gson使用我指定的编译时类型,而不是列表中对象的运行时类型。 I am also using an abstract superclass for A.java , but the example below has the same problem. 我也为A.java使用了一个抽象超类,但是下面的例子有同样的问题。

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

Output: 输出:

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

Expected: 预期:

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

This is because of how Gson serializes collections by default. 这是由于Gson默认情况下如何序列化集合。

Why does this happen? 为什么会这样?

Scroll to the bottom if you don't care about why and just want a fix. 如果您不在乎原因,请滚动到底部,只需要修复即可。

Gson's default CollectionTypeAdapterFactory wraps it's element type adapters in something called a TypeAdapterRuntimeTypeWrapper . Gson的默认CollectionTypeAdapterFactory将其元素类型适配器包装在称为TypeAdapterRuntimeTypeWrapper东西中。 When choosing the proper adapter, it uses 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

In this case, the Third preference is an adapter for B , and the Fourth preference is an adapter for A. This is not avoidable when using the default serializers, as there's no conditional in the CollectionTypeAdapterFactory : 在这种情况下,第三个优先级是B的适配器,而第四个优先级是A的适配器。在使用默认序列化程序时,这是不可避免的,因为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;
}

This wrapper is not present when not using the CollectionTypeAdapterFactory , which is why it doesn't happen in your first two examples. 不使用CollectionTypeAdapterFactory ,不存在此包装器,这就是为什么在前两个示例中不发生这种包装的原因。

tl;dr Okay so how do I fix it? tl; dr好吧,我该如何解决?

The only way to get around this problem is to register a custom serializer. 解决此问题的唯一方法是注册自定义序列化程序。 Writing one for A will do the trick, in your use case: 在您的用例中,为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();
  }
}

Then, if you do: 然后,如果您这样做:

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

You get the expected output: 您将获得预期的输出:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM