У меня есть структура 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
будет сохранено). Есть ли умный способ, который я пропустил?