Джерисон десериализационный адаптер / конвертер с контекстом

1

У меня есть простой набор классов, которые я хочу сериализовать в /deserialize из YAML с помощью Jackson (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 (subtype). Я могу писать и читать их просто отлично. Проблема, с которой я сталкиваюсь, заключается в том, чтобы отдельно сериализовать/десериализовать объекты другого класса:

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));

Джексон поддерживает аннотации 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.

Теги:
serialization
jackson
yaml

1 ответ

0

Если я правильно прочитаю ваш вопрос, ваши сериализованные записи PetOwner не должны содержать полные записи Animal - они просто должны содержать строку для каждой записи Animal. И ваши осложнения исходят из того факта, что вы тем не менее сохраняете записи Animal.

Предполагая, что для точной формулировки один подход будет заключаться в том, чтобы аннотировать ваше поле "домашние животные" (или геттер, не уверен, как выглядит ваш полный класс PetOwner) с помощью @JsonIgnore, затем добавьте геттер, например:

public List<String> getAnimalNames() {
  // return a list containing the name of each Animal in this.pets 
}

Затем вы можете:

  1. Добавьте альтернативный конструктор для PetOwner, который принимает List<String> а не List<Animal>, и аннотирует этот конструктор с помощью @JsonCreator, чтобы Джексон знал его использовать (и создайте экземпляры Animal из предоставленных имен внутри конструктора)
  2. Если вы используете конструктор по умолчанию (0-arg), добавьте установщик, например (и удалите все существующие сеттеры для домашних животных):

public void setAnimalNames(List<String> names) {//populate this.pets by looking up each pet by name in your registry }

Мне нужно будет увидеть больше вашего класса PetOwner и/или узнать больше о том, как вы планируете использовать его, чтобы дать более подробный ответ, но я думаю, что этот общий подход (аннотировать класс PetOwner, чтобы Джексон просто игнорировал "домашних животных" и делал только в 'animalNames') должен дать вам то, что вы ищете.

  • 0
    PetOwner - это просто класс данных. Он не имеет (и не должен получать) доступ к контексту ( AnimalRegistry ), который позволил бы ему сопоставлять имена животных с животными. Так что, к сожалению, ни одно из ваших предложений не сработает для меня.
  • 0
    Это имеет смысл, я даже не думал об этом. Один из подходов, который я часто использую для такого рода вещей, заключается в определении класса данных, который используется строго для персистентности / транспорта (например, он аннотирован, имеет изменяемые поля с установщиками и т. Д.), Который предоставляет метод для преобразования его в «настоящую» версию. того же класса - и я предполагаю, что метод преобразования может быть параметризован таким ресурсом, как AnimalRegistry. Тем не менее, типичная жалоба на такой подход заключается в том, что это слишком много кода ... поэтому, учитывая, что вы надеетесь сократить код, этот подход также может не сработать для вас.
Показать ещё 1 комментарий

Ещё вопросы

Сообщество Overcoder
Наверх
Меню