JSON сериализует только поля базового класса, когда передается ссылка с типом базового класса - PullRequest
0 голосов
/ 18 апреля 2020

Я использую библиотеку Gson . Что такое чистый / идиоматический c способ попросить Gson сериализовать только поля базового класса, когда объект, передаваемый Gson, имеет тип базового класса? Обратите внимание, что это отличается от аналогичных вопросов (например, этот ), которые задают, как всегда исключать указанные c поля. В моем случае использования я хочу исключить наследуемые поля класса только тогда, когда библиотеке Gson передается объект производного класса через ссылку на тип базового класса. В противном случае, т. Е. Если библиотеке Gson передается объект производного класса через типизированную ссылку на производный класс, я хочу, чтобы поля появлялись в сериализации.

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) действительно вернул:

{"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}}

Ответы [ 2 ]

1 голос
/ 18 апреля 2020

Есть ли чистый способ добиться этого?

Да.

Вам не нужны стратегии исключения, хотя они достаточно гибки, чтобы охватить такой сценарий. Сериализация и десериализация классов пакетов данных обеспечивается реализацией TypeAdapter, найденной в ReflectiveTypeAdapterFactory. Этот адаптер типа учитывает наиболее конкретные поля класса .

С учетом сказанного, единственное, что вам нужно, это указание наиболее конкретного типа для получения полей:

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

Причина, по которой он не работает для вас, заключается в том, что в вашем коде указан самый конкретный класс неявно , как если бы вы использовали gson.toJson(o, o.getClass()) или gson.toJson(o, B.class) для кода выше.

Обобщенная проблема c типов

Боюсь, ReflectiveTypeAdapterFactory не может учитывать параметры типа для такого сценария. Не уверен, что именно делает метод getRuntimeTypeIfMoreSpecific и для чего он предназначен, но он «конвертирует» тип A в конкретный тип B из-за проверки type instanceof Class<?>, которая позволяет перезаписывать тип поля с 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.

1 голос
/ 18 апреля 2020

В руководстве пользователя Gson отсутствует прямая конфигурация для этого. Но есть кое-что, что стоит попробовать:

Вы можете использовать сериализатор Gson, чтобы использовать динамический c ExclusionStrategy. Здесь стратегия «Dynami c» означает, что каждый раз, когда вам нужно сериализовать класс Foo, вам потребуется экземпляр Gson, настроенный с OnlyFieldsFor<Foo> startegy.

ExclusionStrategy это интерфейс, который определяет 2 метода:

  1. shouldSkipClass() - Используйте это для отфильтровывания ненужных вам классов.

  2. shouldSkipField() - Это полезно. Он вызывается с FieldAttributes, который содержит информацию о классе, в котором были объявлены поля. Возвращает false, если поля не были объявлены в базовом классе.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...