Jackson正在使用上下文反序列化适配器/转换器



我有一组简单的类,我想使用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 
}

然后,您可以:

  1. 为PetOwner添加一个采用List<String>而非List<Animal>的备用构造函数,并用@JsonCreator注释该构造函数,以便Jackson知道使用它(并根据构造函数内提供的名称构造Animal实例)
  2. 如果您使用默认的(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);
    }
}

最新更新