Десериализация полиморфизма c JSON с помощью переходных переменных с использованием Gson-extras - PullRequest
3 голосов
/ 19 января 2020

У меня есть несколько полиморфных c java классов, которые я сериализую / десериализую. Я использую RuntimeTypeAdapterFactory из Gson-extras, чтобы обеспечить правильную сериализацию и десериализацию классов с полем «type», установленным на имя производного класса. Это работает нормально.

У меня есть некоторые другие классы, которые я сериализую / десериализирую, у которых есть некоторые переменные с примененным модификатором переходного процесса. Я использую PostConstructAdapterFactory, также из Gson-extras, чтобы добавить метод PostConstruct в мои классы, чтобы значения переходных переменных могли быть переназначены после завершения десериализации и до вызова любой из функций в классах. Это также работает нормально.

Проблема, с которой я столкнулся, заключается в том, что у меня есть другая группа классов, которые являются полиморфными c, а также имеют временные переменные, где я хотел бы, чтобы метод PostConstruct выполнялся после десериализации. Все мои попытки использовать одновременно RuntimeTypeAdapterFactory и PostConstructAdapterFactory не увенчались успехом. Я могу зарегистрировать и то и другое, но когда я это делаю, поведение PostConstructAdapterFactory, кажется, перезаписывает поведение RuntimeTypeAdapterFactory, в результате чего мои классы больше не сохраняют поле «тип», необходимое для классов polymorphi c.

Кажется, я могу иметь одно или другое, но не оба. Я даже смотрел на написание гибридного класса AdapterFactory с возможностями обеих фабрик адаптеров, но это тоже было неудачно.

Я чувствую, что здесь может быть упущено что-то очевидное в том, как они спроектированы. Конечно, можно заставить обе части функциональности работать вместе для моих классов? Ниже приведен простой пример, демонстрирующий то, что я говорю.

public class BaseClass {
    private static final RuntimeTypeAdapterFactory<BaseClass> ADAPTER = RuntimeTypeAdapterFactory.of(BaseClass.class);
    private static final HashSet<Class<?>> REGISTERED_CLASSES= new HashSet<Class<?>>();
    static { GsonUtils.registerType(ADAPTER); }

    private synchronized void registerClass() {
        if (!REGISTERED_CLASSES.contains(this.getClass())) {
            REGISTERED_CLASSES.add(this.getClass());
            ADAPTER.registerSubtype(this.getClass());
        }
    }

    public BaseClass() {
        registerClass();
    }
}

public class DerivedClass extends BaseClass {
   public DerivedClass(Integer number) {
       super();
       this.number = number;
       Init();
   }

   protected Integer number;
   protected transient Integer doubled;
   protected transient Integer tripled;
   protected transient Integer halved;
   protected transient Integer squared;
   protected transient Integer cubed;

   public void Init() {
       halved = number / 2;
       doubled = number * 2;
       tripled = number * 3;
       squared = number * number;
       cubed = number * number * number;
   }

   @PostConstruct
   private void postConstruct() {
       Init();
   }

}

public class GsonUtils {
    private static final GsonBuilder gsonBuilder = new GsonBuilder().registerTypeAdapterFactory(new PostConstructAdapterFactory());

    public static void registerType(RuntimeTypeAdapterFactory<?> adapter) {
        gsonBuilder.registerTypeAdapterFactory(adapter);
    }

    public static Gson getGson() {
        return gsonBuilder.create();
    }    
}

Gson gson = GsonUtils.getGson(); 
String serialized = gson.toJson(new DerivedClass(6), BaseClass.class);
DerivedClass dc = gson.fromJson(serialized, DerivedClass.class);

Обновление: спасибо за ответ @ michał-ziober. Выполнение, как вы сказали, действительно сработало. Это действительно отвечает на вопрос, однако, это все еще точно не решает мою проблему. Позвольте мне немного изменить вопрос.

Упрощенная версия моего кода была, пожалуй, слишком простой. Когда я пытаюсь сериализовать / десериализовать DerivedClass как свойство другого класса, моя первоначальная проблема высвечивается. Как показано ниже:

public class WrapperClass {
    protected BaseClass dc = new DerivedClass(6);
}

Gson gson = GsonUtils.getGson(); 
String serialized = gson.toJson(new WrapperClass());
WrapperClass wc = gson.fromJson(serialized, WrapperClass.class);

В этом сценарии, пока мой DerivedClass не определяет метод PostConstruct, сериализация / десериализация DerivedClass работает нормально. Но если это так, свойство "type" не записывается в файл.

Просто для повторения, если я сериализую / десериализую DerivedClass напрямую, все нормально, но если он внутри WrapperClass и я сериализую / десериализуем WrapperClass И если я определю метод PostConstruct, он не будет работать.

1 Ответ

1 голос
/ 21 января 2020

Все отлично с адаптерами типа, и вы можете использовать их оба одновременно. В вашем случае проблема в том, в каком коде выполняется.

  1. GsonUtils.getGson() - создает Gson объект с адаптерами.
  2. gson.toJson(new DerivedClass(6), BaseClass.class) - вы создаете DerivedClass экземпляр, который выполняет супер-конструктор из BaseClass, который регистрирует данный класс в адаптере (si c!).

Попробуйте этот код:

DerivedClass src = new DerivedClass(6);
Gson gson1 = GsonUtils.getGson();
String serialized = gson1.toJson(src, BaseClass.class);
System.out.println(serialized);
DerivedClass dc = gson1.fromJson(serialized, DerivedClass.class);

Он будет работать как положено , Создание DerivedClass имеет побочный эффект - это действительно хороший пример того, почему классы должны быть отделены друг от друга. У вас не должно быть Gson специфицированных c классов в BaseClass.

В лучшем мире BaseClass должен содержать только общие свойства и некоторые базовые логи c:

class BaseClass {
    // getters, setters, business logic.
}

Все Gson конфигурации должны быть помещены в GsonUtils класс:

class GsonUtils {

    public static Gson getGson() {
        return new GsonBuilder()
                .registerTypeAdapterFactory(RuntimeTypeAdapterFactory.of(BaseClass.class)
                        .registerSubtype(DerivedClass.class))
                .registerTypeAdapterFactory(new PostConstructAdapterFactory())
                .create();
    }
}

Если вы не хотите указывать все классы вручную, вам нужно сканировать среду во время выполнения. Взгляните на: Во время выполнения найдите все классы в Java приложении, расширяющем базовый класс .

Редактирование после обновления вопроса

Похоже TypeAdapterFactory цепочка не так проста, как я ожидал. От данной реализации TypeAdapterFactory зависит, вернет ли она null или вернет новый объект с некоторым делегированием на другие фабрики.

Я нашел обходной путь, но его было бы нелегко использовать в реальном проекте:

  1. Я создал два Gson объекта: один для сериализации и один для десериализации. Только для процесса десериализации мы регистрируем PostConstructAdapterFactory.
  2. Мы добавляем метод с аннотацией @PostConstruct к BaseClass.

Пример:

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.typeadapters.PostConstructAdapterFactory;
import com.google.gson.typeadapters.RuntimeTypeAdapterFactory;

import javax.annotation.PostConstruct;

public class GsonApp {

    public static void main(String[] args) {
        RuntimeTypeAdapterFactory<BaseClass> typeFactory = RuntimeTypeAdapterFactory.of(BaseClass.class)
                .registerSubtype(DerivedClass.class);

        Gson serializer = new GsonBuilder()
                .registerTypeAdapterFactory(typeFactory)
                .create();

        Gson deserializer = new GsonBuilder()
                .registerTypeAdapterFactory(typeFactory)
                .registerTypeAdapterFactory(new PostConstructAdapterFactory())
                .create();

        WrapperClass wrapper = new WrapperClass();
        wrapper.setDc(new DerivedClass(8));

        String json = serializer.toJson(wrapper);
        System.out.println(json);
        System.out.println(deserializer.fromJson(json, WrapperClass.class));
    }

}

class WrapperClass {
    protected BaseClass dc;

    public BaseClass getDc() {
        return dc;
    }

    public void setDc(BaseClass dc) {
        this.dc = dc;
    }

    @Override
    public String toString() {
        return "WrapperClass{" +
                "dc=" + dc +
                '}';
    }
}

class BaseClass {

    @PostConstruct
    protected void postConstruct() {
    }
}

class DerivedClass extends BaseClass {
    public DerivedClass(Integer number) {
        super();
        this.number = number;
        Init();
    }

    protected Integer number;
    protected transient Integer doubled;
    protected transient Integer tripled;
    protected transient Integer halved;
    protected transient Integer squared;
    protected transient Integer cubed;

    public void Init() {
        halved = number / 2;
        doubled = number * 2;
        tripled = number * 3;
        squared = number * number;
        cubed = number * number * number;
    }

    @PostConstruct
    protected void postConstruct() {
        Init();
    }

    @Override
    public String toString() {
        return "DerivedClass{" +
                "number=" + number +
                ", doubled=" + doubled +
                ", tripled=" + tripled +
                ", halved=" + halved +
                ", squared=" + squared +
                ", cubed=" + cubed +
                "} ";
    }
}

Над кодом отпечатки:

{"dc":{"type":"DerivedClass","number":8}}
WrapperClass{dc=DerivedClass{number=8, doubled=16, tripled=24, halved=4, squared=64, cubed=512} }
...