我有一组简单的类,我想使用Jackson(2.4.5)将它们序列化到YAML/从YAML反序列化:
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
的名称列表。当反序列化时,我想使用预先存在的AnimalRegistry
将这些名称映射回Animal
对象。
使用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)
并将CCD_ 9的实例添加到CCD_ 10/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)
并在CCD_ 17:上设置CCD_
mapper.setHandlerInstantiator(
new AnimalHandlerInstantiator(animalRegistry));
这是可行的,但它包含了大量的代码。有人能提出一个更简洁的替代方案吗?不过,我希望避免为PetOwner
编写需要手动处理除pets
之外的字段的序列化程序/反序列化程序。
如果我没看错你的问题,你的序列化PetOwner记录不需要包含完整的Animal记录——它们只需要为每个Animal纪录包含一个String。你的复杂性来自于你存储动物记录的事实。
假设这是一个准确的语句,一种方法是用@JsonIgnore注释"pets"字段(或getter,不确定完整的PetOwner类是什么样子),然后添加一个getter,例如:
public List<String> getAnimalNames() {
// return a list containing the name of each Animal in this.pets
}
然后,您可以:
- 为PetOwner添加一个采用
List<String>
而非List<Animal>
的备用构造函数,并用@JsonCreator注释该构造函数,以便Jackson知道使用它(并根据构造函数内提供的名称构造Animal实例) - 如果您使用默认的(0-arg)构造函数,请添加一个setter,例如(并删除任何现有的宠物setter):
public void setAnimalNames(List<String> names) {
// populate this.pets by looking up each pet by name in your registry
}
我必须更多地了解你的PetOwner类和/或了解你计划如何使用它来给出更详细的响应,但我认为这种通用方法(注释PetOwner类别,这样Jackson就可以忽略"宠物",只使用"animalNames")应该能给你带来你想要的东西。
对于这个问题,这里有一种不同的方法,不需要使用转换器,而是直接在解串器中处理转换:
AnimalRegistry fRegistry = ...
ObjectMapper objectMapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.setDeserializerModifier(new BeanDeserializerModifier() {
@Override
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc,
JsonDeserializer<?> deserializer) {
if (Animal.class.isAssignableFrom(beanDesc.getBeanClass())) {
return new AnimalDeserializer(fRegistry, deserializer, beanDesc.getBeanClass());
}
return deserializer;
}
});
objectMapper.registerModule(module);
public class AnimalDeserializer extends StdDeserializer<Animal> implements ResolvableDeserializer {
private static final long serialVersionUID = 1L;
private final JsonDeserializer<?> defaultDeserializer;
private final AnimalRegistry registry;
public AnimalDeserializer(AnimalRegistry, JsonDeserializer<?> defaultDeserializer, Class<?> clazz) {
super(clazz);
this.registry = registry;
this.defaultDeserializer = defaultDeserializer;
}
@Override
public Animal deserialize(JsonParser parser, DeserializationContext context) throws IOException {
Animal animal = (Animal) defaultDeserializer.deserialize(parser, context);
// no add here your converter logic and filter the animals
return Animal;
}
@Override
public void resolve(DeserializationContext ctxt) throws JsonMappingException {
((ResolvableDeserializer) defaultDeserializer).resolve(ctxt);
}
}