У меня есть простой набор классов, которые я хочу, чтобы сериализации/десериализации из YAML, используя Джексон (2.4.5):Джексон десериализации адаптер/конвертер с контекстом
public static class Animal {
public String name;
}
public static class Dog extends Animal {
public String breed;
}
public static class Cat extends Animal {
public String favoriteToy;
}
public static class AnimalRegistry {
private Map<String, Animal> fAnimals = new HashMap<>();
public AnimalRegistry(Animal... animals) {
for (Animal animal : animals)
fAnimals.put(animal.name, animal);
}
public Animal getAnimal(String name) {
return fAnimals.get(name);
}
}
Это довольно прямо вперед, чтобы сделать это так что AnimalRegistry
заканчивается как список вложенных объектов объектов Animal
(подтип). Я могу писать и читать их просто отлично. Проблема я столкнулся это отдельно сериализации/десериализации объектов другого класса:
public static class PetOwner {
public String name;
public List<Animal> pets = new ArrayList<>();
}
Я не хочу, чтобы сериализовать Animal
объекты как список вложенных объектов, а только хранить список из Animal
. При десериализации я хочу нанести эти имена обратно на Animal
объектов с использованием существующего AnimalRegistry
.
С JAXB я мог бы просто сделать это используя XmlAdapter
:
public static class PetXmlAdapter extends XmlAdapter<String, Animal> {
private AnimalRegistry fRegistry;
public PetXmlAdapter(AnimalRegistry registry) {
fRegistry = registry;
}
@Override
public Animal unmarshal(String value) throws Exception {
return fRegistry.getAnimal(value);
}
@Override
public String marshal(Animal value) throws Exception {
return value.name;
}
}
я аннотировать pets
поле с
@XmlJavaTypeAdapter(value = PetXmlAdapter.class)
и добавить экземпляр PetXmlAdapter
к Marshaller
/Unmarshaller
:
marshaller.setAdapter(new PetXmlAdapter(animalRegistry));
...
unmarshaller.setAdapter(new PetXmlAdapter(animalRegistry));
Jackson поддерживает аннотации JAXB и может использовать один и тот же класс PetXmlAdapter
, но я не вижу способа установить его экземпляр на ObjectMapper
или любой родственный класс и, следовательно, не могу использовать мои ранее существовавшие AnimalRegistry
.
Джексон, кажется, есть много точек для настройки и в конце концов я нашел способ для достижения своей цели:
public static class AnimalNameConverter
extends StdConverter<Animal, String> {
@Override
public String convert(Animal value) {
return value != null ? value.name : null;
}
}
public static class NameAnimalConverter
extends StdConverter<String, Animal> {
private AnimalRegistry fRegistry;
public NameAnimalConverter(AnimalRegistry registry) {
fRegistry = registry;
}
@Override
public Animal convert(String value) {
return value != null ? fRegistry.getAnimal(value) : null;
}
}
public static class AnimalSerializer
extends StdDelegatingSerializer {
public AnimalSerializer() {
super(Animal.class, new AnimalNameConverter());
}
private AnimalSerializer(Converter<Object,?> converter,
JavaType delegateType,
JsonSerializer<?> delegateSerializer) {
super(converter, delegateType, delegateSerializer);
}
@Override
protected StdDelegatingSerializer withDelegate(
Converter<Object, ?> converter, JavaType delegateType,
JsonSerializer<?> delegateSerializer) {
return new AnimalSerializer(converter, delegateType,
delegateSerializer);
}
}
public static class AnimalDeserializer
extends StdDelegatingDeserializer<Animal> {
private static final long serialVersionUID = 1L;
public AnimalDeserializer(AnimalRegistry registry) {
super(new NameAnimalConverter(registry));
}
private AnimalDeserializer(Converter<Object, Animal> converter,
JavaType delegateType,
JsonDeserializer<?> delegateDeserializer) {
super(converter, delegateType, delegateDeserializer);
}
@Override
protected StdDelegatingDeserializer<Animal> withDelegate(
Converter<Object, Animal> converter,
JavaType delegateType,
JsonDeserializer<?> delegateDeserializer) {
return new AnimalDeserializer(converter, delegateType,
delegateDeserializer);
}
}
public static class AnimalHandlerInstantiator
extends HandlerInstantiator {
private AnimalRegistry fRegistry;
public AnimalHandlerInstantiator(AnimalRegistry registry) {
fRegistry = registry;
}
@Override
public JsonDeserializer<?> deserializerInstance(
DeserializationConfig config, Annotated annotated,
Class<?> deserClass) {
if (deserClass != AnimalDeserializer.class)
return null;
return new AnimalDeserializer(fRegistry);
}
@Override
public KeyDeserializer keyDeserializerInstance(
DeserializationConfig config, Annotated annotated,
Class<?> keyDeserClass) {
return null;
}
@Override
public JsonSerializer<?> serializerInstance(
SerializationConfig config, Annotated annotated,
Class<?> serClass) {
return null;
}
@Override
public TypeResolverBuilder<?> typeResolverBuilderInstance(
MapperConfig<?> config, Annotated annotated,
Class<?> builderClass) {
return null;
}
@Override
public TypeIdResolver typeIdResolverInstance(
MapperConfig<?> config,
Annotated annotated, Class<?> resolverClass) {
return null;
}
}
Я аннотировать pets
поле с
@JsonSerialize(contentUsing = AnimalSerializer.class)
@JsonDeserialize(contentUsing = AnimalDeserializer.class)
и установить экземпляр AnimalHandlerInstantiator
на ObjectMapper
:
mapper.setHandlerInstantiator(
new AnimalHandlerInstantiator(animalRegistry));
Это работает, но это очень много кода. Может ли кто-нибудь предложить более краткий вариант? Я хотел бы избежать написания сериализатора/десериализатора для PetOwner
, который требует ручной обработки полей, отличных от pets
.
'PetOwner' - это всего лишь класс данных. Он не имеет (и не должен) доступа к контексту ('AnimalRegistry'), который позволил бы ему отображать имена животных для животных. Поэтому, к сожалению, ни одно из ваших предложений не будет работать для меня. – user686249
Это имеет смысл, я даже об этом не думал. Один из подходов, который я часто использую для такого рода вещей, - это определить класс данных, который используется строго для сохранения/переноса (например, он аннотирован, имеет изменяемые поля с сеттерами и т. Д.), Который предоставляет метод преобразования его в «реальную» версию того же класса - и я полагаю, что метод преобразования может быть параметризован ресурсом, таким как AnimalRegistry. Однако типичная жалоба на такой подход заключается в том, что слишком много кода ... поэтому, учитывая, что вы надеетесь сократить код, этот подход может и не сработать для вас. – wdf
Действительно, я надеялся, что решение будет не намного тяжелее, чем подход XmlAdapter для JAXB. – user686249