简体   繁体   English

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

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

I am using the Gson library.我正在使用Gson库。 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? 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 (eg this one ) which ask how to always exclude specific fields.请注意,这与询问如何始终排除特定字段的类似问题(例如this one )不同。 In my use case I only want the inherited class fields excluded when the Gson library is passed a derived class object through a base class-typed reference.在我的用例中,我只希望在 Gson 库通过派生的 class ZA8CFDE6331typedC4666 参考基类传递时排除继承的 class 字段。 Otherwise, ie if the Gson library is passed a derived class object through a derived-class-typed reference then I want the fields to appear in the serialization.否则,即如果 Gson 库通过派生类类型引用传递派生 class object ,那么我希望这些字段出现在序列化中。

SSCCE follows: 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));
    }
}

The above prints:以上打印:

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

I am looking for a clean way to make it print:我正在寻找一种干净的方式来打印它:

{"a":42} {“一”:42}

However if the following code is used:但是,如果使用以下代码:

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

... then I want gson.toJson(b) to indeed return: ...然后我希望gson.toJson(b)确实返回:

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

Is there a clean way to achieve that?有没有一种干净的方法来实现这一目标?

UPDATE更新

The accepted answer at the time of this writing suggests using toJson(o, A.class) which does work in this case.在撰写本文时,公认的答案建议使用在这种情况下确实有效的toJson(o, A.class) However, it appears that this method does not scale well to generics.但是,这种方法似乎不能很好地扩展到 generics。 Eg:例如:

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

Regrettably, the above prints:遗憾的是,上面的打印:

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

There appears to be no straight forward configuration for this in Gson user manual. Gson 用户手册中似乎没有直接的配置。 But there is something worth trying:但有一点值得一试:

You can use the Gson serializer to use a dynamic ExclusionStrategy .您可以使用 Gson 序列化程序来使用动态ExclusionStrategy Here, "Dynamic" strategy means every time you need to serialize a Class Foo , you'll need a Gson instance configured with OnlyFieldsFor<Foo> startegy.在这里,“动态”策略意味着每次您需要序列化 Class Foo时,您都需要一个配置了OnlyFieldsFor<Foo> startegy 的 Gson 实例。

ExclusionStrategy is an interface, which specifies 2 methods: ExclusionStrategy是一个接口,它指定了 2 个方法:

  1. shouldSkipClass() - Use this to filter out classes you don't want. shouldSkipClass() - 使用它来过滤掉你不想要的类。

  2. shouldSkipField() - This is useful. shouldSkipField() - 这很有用。 It is called with FieldAttributes , which has information about the class the fields was declared in. Return false if the fields was not declared in the base class.它使用FieldAttributes调用,其中包含有关声明字段的 class 的信息。如果未在基础 class 中声明字段,则返回 false。

Is there a clean way to achieve that?有没有一种干净的方法来实现这一目标?

Yes.是的。

You don't need exclusion strategies despite they are flexible enough to cover such a scenario.尽管排除策略足够灵活,可以涵盖这种情况,但您不需要排除策略。 Data bag classes serialization and deserialization is driven by TypeAdapter implementation found in ReflectiveTypeAdapterFactory .数据包类的序列化和反序列化由ReflectiveTypeAdapterFactory中的TypeAdapter实现驱动。 This type adapter takes the most concrete class fields into account.这种类型的适配器考虑了最具体的 class 字段

Having that said, the only thing you need is specifying the most concrete type to take fields from:话虽如此,您唯一需要的是指定最具体的类型来获取字段:

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

The reason of why it was not working for you is that your code specified the most concrete class implicitly , just like if you'd use gson.toJson(o, o.getClass()) or gson.toJson(o, B.class) for the code above.它对您不起作用的原因是您的代码隐含地指定了最具体的 class ,就像您使用gson.toJson(o, o.getClass())gson.toJson(o, B.class)对于上面的代码。

The generic types problem泛型类型问题

I'm afraid ReflectiveTypeAdapterFactory can't take type parameters into account for such a scenario.恐怕ReflectiveTypeAdapterFactory在这种情况下无法考虑类型参数。 Not sure what the getRuntimeTypeIfMoreSpecific method exactly does and what it's original purpose, but it "converts" the A type to the concrete B type because of the type instanceof Class<?> check that allows to overwrite the field type with type = value.getClass();不确定getRuntimeTypeIfMoreSpecific方法究竟做了什么以及它的最初目的是什么,但它会将A类型“转换”为具体的B类型,因为type instanceof Class<?>检查允许使用type = value.getClass(); 覆盖字段类型type = value.getClass(); . . It looks like you must implement a workaround for such a case:看起来您必须针对这种情况实施一种解决方法:

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

Unfortunately having this type adapter in your Gson instance makes the @SerializedName , @JsonAdapter and other cool things unavailable and makes it stick to hardcoded field names.不幸的是,在您的Gson实例中使用此类型适配器会使@SerializedName@JsonAdapter和其他很酷的东西不可用,并使其坚持硬编码的字段名称。 This is probably a Gson bug.这可能是一个 Gson 错误。

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

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