2015-02-05 5 views
1

У меня есть простой набор классов, которые я хочу, чтобы сериализации/десериализации из 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.

ответ

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 так, что Джексон знает, чтобы использовать его (и построить ваш Экземпляры животных из е при условии имена внутри конструктора)
  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'), который позволил бы ему отображать имена животных для животных. Поэтому, к сожалению, ни одно из ваших предложений не будет работать для меня. – user686249

+0

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

+0

Действительно, я надеялся, что решение будет не намного тяжелее, чем подход XmlAdapter для JAXB. – user686249

Смежные вопросы