JSON 当传递基 class 类型的引用时,仅序列化基 class 字段

JSON serialize only base class fields when a base class-typed reference is passed

我正在使用 Gson library. What is a clean / idiomatic way to ask Gson to serialize only the fields of the base class when the object give to Gson is of the base class type? Note that this is different from similar questions (e.g. this one) 询问如何始终排除特定字段。在我的用例中,当 Gson 库通过基础 class 类型的引用传递派生的 class 对象时,我只希望排除继承的 class 字段。否则,即如果 Gson 库通过派生的 class 类型的引用传递派生的 class 对象,那么我希望这些字段出现在序列化中。

SSCCE 如下:

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

class A {
    public int a;
    public A(final int a) {
        this.a = a;
    }
}

class B extends A {
    public int b;

    public B(final int a, final int b) {
        super(a);
        this.b = b;
    }

}

public class Main {

    public static void main(String args[]) {

        final A a = new B(42, 142);
        final Gson gson = new GsonBuilder().serializeNulls().create();

        System.out.printf("%s\n", gson.toJson(a));
    }
}

以上打印:

{"b":142,"a":42}

我正在寻找一种干净的打印方式:

{"a":42}

但是如果使用下面的代码:

final B b = new B(42, 142);

... 然后我想要 gson.toJson(b) 确实 return:

{"b":142,"a":42}

有没有一种干净的方法来实现它?

更新

建议使用 toJson(o, A.class),这在这种情况下确实有效。但是,这种方法似乎不能很好地扩展到泛型。例如:

class A {
    public int a;
    public A(final int a) {
        this.a = a;
    }
}

class B extends A {
    public int b;

    public B(final int a, final int b) {
        super(a);
        this.b = b;
    }
}

class Holder<T> {
    public final T t;

    public Holder(final T t) {
        this.t = t;
    }
}

final A a = new B(42, 142);
final Holder<A> holder = new Holder<A>(a);
final Gson gson = new GsonBuilder().serializeNulls().create();

final Type TYPE= new TypeToken<Holder<A>>() {}.getType();
System.out.printf("%s\n", gson.toJson(holder, TYPE));

遗憾的是,上面的打印:

{"t":{"b":142,"a":42}}

Gson 用户手册中似乎没有针对此的直接配置。但有些东西值得一试:

您可以使用 Gson 序列化程序来使用动态 ExclusionStrategy。这里,"Dynamic" 策略意味着每次你需要序列化一个 Class Foo,你需要一个配置了 OnlyFieldsFor<Foo> startegy 的 Gson 实例。

ExclusionStrategy是一个接口,它指定了2个方法:

  1. shouldSkipClass() - 使用它来过滤掉您不想要的 classes。

  2. shouldSkipField() - 这很有用。它通过 FieldAttributes 调用,其中包含有关在其中声明字段的 class 的信息。如果字段未在基础 class.[=20 中声明,则 Return false =]

Is there a clean way to achieve that?

是的。

您不需要排除策略,尽管它们足够灵活以涵盖这种情况。数据包 classes 序列化和反序列化由 ReflectiveTypeAdapterFactory. This type adapter takes the most concrete class fields 中发现的 TypeAdapter 实现驱动。

话虽如此,您唯一需要的是 specifying 从中获取字段的最具体类型:

final Object o = new B(42, 142);
System.out.println(gson.toJson(o, A.class));

它不适合你的原因是你的代码指定了最具体的 class implicitly,就像你使用 gson.toJson(o, o.getClass()) 或 [=15] =]上面的代码。

泛型类型问题

恐怕 ReflectiveTypeAdapterFactory 无法在这种情况下考虑类型参数。 不确定 getRuntimeTypeIfMoreSpecific 方法到底做了什么以及它的原始目的是什么,但是由于 type instanceof Class<?> 检查,它 "converts" 将 A 类型转换为具体的 B 类型允许 overwrite 具有 type = value.getClass(); 的字段类型。 看来您必须为这种情况实施解决方法:

.registerTypeAdapterFactory(new TypeAdapterFactory() {
    @Override
    public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
        final Class<? super T> rawType = typeToken.getRawType();
        if ( rawType != Holder.class ) {
            return null;
        }
        final Type valueType = ((ParameterizedType) typeToken.getType()).getActualTypeArguments()[0];
        @SuppressWarnings({"unchecked", "rawtypes"})
        final TypeAdapter<Object> valueTypeAdapter = (TypeAdapter) gson.getDelegateAdapter(this, TypeToken.get(valueType));
        final TypeAdapter<Holder<?>> typeAdapter = new TypeAdapter<Holder<?>>() {
            @Override
            public void write(final JsonWriter out, final Holder<?> value)
                    throws IOException {
                out.beginObject();
                out.name("t");
                valueTypeAdapter.write(out, value.t);
                out.endObject();
            }

            @Override
            public Holder<?> read(final JsonReader in)
                    throws IOException {
                Object t = null;
                in.beginObject();
                while ( in.hasNext() ) {
                    final String name = in.nextName();
                    switch ( name ) {
                    case "t":
                        t = valueTypeAdapter.read(in);
                        break;
                    default:
                        // do nothing;
                        break;
                    }
                }
                in.endObject();
                return new Holder<>(t);
            }
        }.nullSafe();
        @SuppressWarnings("unchecked")
        final TypeAdapter<T> castTypeAdapter = (TypeAdapter<T>) typeAdapter;
        return castTypeAdapter;
    }
})

不幸的是,在您的 Gson 实例中使用这种类型的适配器会使 @SerializedName@JsonAdapter 和其他很酷的东西不可用,并使其坚持硬编码字段名称。 这可能是一个 Gson 错误。