Gson не записывает значения в объекты, созданные с помощью IntanceCreator - PullRequest
1 голос
/ 13 апреля 2020

Я использую gson 2.8.6, и это мой код:

interface Foo {

    String getTest();
}

class FooImpl implements Foo {

    private String test;

    @Override
    public String getTest() {
        return this.test;
    }

    public void setTest(String test) {
        this.test = test;
    }

    @Override
    public String toString() {
        return "FooImpl{" + "test=" + test + '}';
    }
}

class Bar {

    private Foo foo;

    public Foo getFoo() {
        return foo;
    }

    public void setFoo(Foo foo) {
        this.foo = foo;
    }

    @Override
    public String toString() {
        return "Bar{" + "foo=" + foo + '}';
    }
}

class FooInstanceCreator implements InstanceCreator<Foo> {  

    @Override
    public Foo createInstance(Type type) {
        return new FooImpl();
    }
}

public class NewMain3 {

    public static void main(String[] args) {
        FooImpl foo = new FooImpl();
        foo.setTest("this is test");

        Bar bar = new Bar();
        bar.setFoo(foo);

        Gson gson = new GsonBuilder()
                    .serializeNulls()
                    .registerTypeAdapter(Foo.class, new FooInstanceCreator())
                    .create();

        //TO
        String to = gson.toJson(bar);
        System.out.println("TO:" + to);

        //FROM
        Bar from = gson.fromJson(to, Bar.class);
        System.out.println("FROM:" + from);
    }
}

И это вывод:

TO:{"foo":{"test":"this is test"}}
FROM:Bar{foo=FooImpl{test=null}}

Как видите, поле test потеряно. Кто-нибудь может сказать, как это исправить?

Ответы [ 2 ]

2 голосов
/ 13 апреля 2020

Я думаю, что идея InstanceCreator обманчиво противоречит принципу работы ReflectiveTypeAdapterFactory:

  • первый имеет дело с классами, которые фактически являются пакетами данных, а не интерфейсами (и об этом говорит пример в интерфейсе JavaDo c);
  • , в то время как последний, согласно исходному коду, разрешает class связанные поля (не интерфейсы, которые не объявляют поля) заранее применение стратегий переопределения для @SerializedName, что повышает сложность.

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

public static <T1, T2 extends T1> TypeAdapterFactory bind(final TypeToken<T1> superTypeToken, final TypeToken<T2> subTypeToken) {
    return new TypeAdapterFactory() {
        @Override
        public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
            if ( !typeToken.equals(superTypeToken) ) {
                return null;
            }
            @SuppressWarnings("unchecked")
            final TypeAdapter<T> typeAdapter = (TypeAdapter<T>) gson.getDelegateAdapter(this, subTypeToken);
            return typeAdapter;
        }
    };
}

, а затем зарегистрировать его в сборщике Gson:

private static final TypeToken<Foo> fooTypeToken = new TypeToken<Foo>() {};
private static final TypeToken<FooImpl> fooImplTypeToken = new TypeToken<FooImpl>() {};

private static final Gson gson = new GsonBuilder()
        .serializeNulls()
        .registerTypeAdapterFactory(bind(fooTypeToken, fooImplTypeToken))
        .create();

Это должно быть идеальным вариантом для вас.

(Пример, предоставленный Dici, является еще одним примером использования: это адаптер типа polymorphi c, который десериализует объект из поддерева JSON на основе JSON свойство, которое определяет тип для создания экземпляра. С другой стороны, метод bind, описанный выше, можно рассматривать как специальную специализацию "без типа" для RuntimeTypeAdapterFactory.)

2 голосов
/ 13 апреля 2020

Запуск отладчика в вашем коде, вот где ваша десериализация останавливается ( ссылка ). Очевидно, что интерфейсы не имеют связанных полей, поэтому он просто создает экземпляр и затем перемещается, ничего не устанавливая.

Вы можете исправить это следующим образом:

final RuntimeTypeAdapterFactory<Foo> typeFactory = RuntimeTypeAdapterFactory
      .of(Foo.class, "type")
      .registerSubtype(FooImpl.class);

Gson gson = new GsonBuilder()
       .serializeNulls()
       .registerTypeAdapterFactory(typeFactory)
       .create();

, где RuntimeTypeAdapterFactory можно найти здесь . Я нашел это в этом вопросе StackOverflow , кстати, не очень глубоко скрытом. Это значит, что он запишет имя класса конкретного класса в объекте JSON, чтобы впоследствии десериализовать его:

TO:{"foo":{"type":"FooImpl","test":"this is test"}}
FROM:Bar{foo=FooImpl{test=this is test}}

У меня нет опыта работы с Gson, но кажется немного менее приятный, чем Джексон в этом специфическом c аспекте.

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