Пользовательский десериализатор Gson, получающий тип одного поля из другого - PullRequest
1 голос
/ 19 февраля 2020

У меня есть структура JSON, где объекты в основном являются объединениями - поле дискриминатора определяет тип объекта, и каждый тип имеет свои свойства. В качестве примера представим структуру Animal:

{
  animals: [
    {
      species: 'DOG',
      data: {
        buriedBones: 8
      }
    },
    {
      species: 'CAT',
      data: {
        hasEvilPlan: true
      }
    }
  ]
}

Со стороны Java я бы хотел, чтобы это было представлено классом с перечислением species дискриминатора и членом data, это может быть либо CatData, либо DogData, что-то вроде:

enum Species {DOG, CAT}

abstract class Data {}
class DogData extends Data {
  int buriedBones;
}
class CatData extends Data {
  boolean hasEvilPlan;
}

public class Animal {
  public Species species;
  public Data data;
}

Это означает, что при сериализации мне не нужно делать ничего особенного, но при десериализации мне нужно прочитать species и из него определить класс объекта данных (DogData.class или CatData.class), а затем десериализовать данные как этот класс и поместить их в поле data.

Для этого я Я написал специальный адаптер типа, который использует аннотации для предоставления карты дискриминатора-> класса данных, и это работает, но я не совсем доволен. Я могу получить дерево JSON, найти значение species и из него класс данных, но я не могу найти умный способ десериализации только поля data с использованием указанного класса c, оставляя все иначе к поведению по умолчанию. Единственное решение, которое я нашел до сих пор, - это удалить свойство data из дерева JSON, вызвать адаптер делегата (в результате animal.data будет null), а затем вручную использовать отражение для установки поля данных. :


discriminator = "species";
...
@Override
public T read(JsonReader in) throws IOException {
  JsonObject tree = treeAdapter.read(in).getAsJsonObject();

  // look up class from map of "DOG"->DogData.class etc
  Type dataType = getDataType(tree.get(discriminator).getAsString()); 
  tree.remove("data");
  T result = delegate.fromJsonTree(tree); // Creates an Animal with data = null

  Field field = result.getClass().getField("data");
  field.setAccessible(true);
  field.set(result, gson.fromJson(data, dataType)); // Create DogData or CatData and assign it to animal.data afterwards

  return result;

Я бы предпочел не прибегать к ручному отражению, но я не могу найти способ использовать адаптер делегата, но говорю это "когда вы достигнете поля data, пожалуйста, используйте класс DogData.class для его десериализации ". Обратите внимание, что я, конечно, мог бы написать TypeAdapter специально для Animal, который просто делает return new Animal(Species.valueOf(species), gson.fromJson(data, dataClass)), но мне нужен универсальный c адаптер, который можно использовать в различных классах, использующих этот шаблон, и любые другие свойства в Класс десериализован по умолчанию.

Я также знаю, что если классы, использующие этот шаблон, будут обобщенными c, как в Animal<D extends Data>, я мог бы, вероятно, десериализовать их, используя TypeToken<Animal<DogData>>, но это также накладывает ненужные ограничения на данные модель. Опять же, то, что мне нужно, это способ только переопределить одно указанное поле c, сказав GSON использовать специфицированный c класс, производный от другого поля, для десериализации его, но затем обработать все другая десериализация автоматически (так что, например, свойство типа animal.nrLegs будет сохранено). Есть ли умный способ, который я пропустил?

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