将Jackson的@JsonTypeInfo与自定义序列化器一起使用

时间:2011-11-15 17:41:20

标签: java json jackson

当我使用自定义序列化程序时,我遇到了Jackson不支持@JsonTypeInfo注释的问题。下面的简化示例不需要自定义序列化,并在不使用自定义序列化程序时按预期输出type属性。但是,一旦启用了自定义序列化程序,就不会写入type属性并阻止反序列化。这是我的实际系统的测试用例,它确实需要一个自定义序列化程序,如果有所不同,则尝试序列化Map<Enum, Map<Enum, T>>其中T是多态类,其类型信息不是书面。如何编写自定义序列化程序以正确处理类型信息?我希望如果我能在下面的测试用例中使用它,我将能够将相同的概念应用于实际的代码。

测试程序尝试尽可能接近地模拟我在实际应用程序中使用的序列化过程,构建Map<>并序列化而不是直接序列化Zoo

预期产出:

{
    "Spike": {
        "type": "dog",
        "name": "Spike",
        "breed": "mutt",
        "leashColor": "red"
    },
    "Fluffy": {
        "type": "cat",
        "name": "Fluffy",
        "favoriteToy": "spider ring"
    }
}

通过在SimpleModule中注释掉自定义序列化程序注册,您可以看到输出的类型信息,但是在注册了序列化程序的情况下,输出如上所示,但没有type属性。

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.JsonProcessingException;
import org.codehaus.jackson.Version;
import org.codehaus.jackson.annotate.JsonCreator;
import org.codehaus.jackson.annotate.JsonProperty;
import org.codehaus.jackson.annotate.JsonSubTypes;
import org.codehaus.jackson.annotate.JsonSubTypes.Type;
import org.codehaus.jackson.annotate.JsonTypeInfo;
import org.codehaus.jackson.annotate.JsonTypeInfo.As;
import org.codehaus.jackson.annotate.JsonTypeInfo.Id;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.JsonSerializer;
import org.codehaus.jackson.map.Module;
import org.codehaus.jackson.map.Module.SetupContext;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.ResolvableSerializer;
import org.codehaus.jackson.map.SerializationConfig.Feature;
import org.codehaus.jackson.map.SerializerProvider;
import org.codehaus.jackson.map.module.SimpleModule;
import org.codehaus.jackson.map.module.SimpleSerializers;

public class JacksonTest {
    public static void main(String[] args) throws Exception {
        ObjectMapper mapper = new ObjectMapper();
        Module m = new SimpleModule("TestModule", new Version(1,0,0,"")) {
            @Override
            public void setupModule(SetupContext context) {
                super.setupModule(context);
                context.setMixInAnnotations(Animal.class, AnimalMixIn.class);

                SimpleSerializers serializers = new SimpleSerializers();
                serializers.addSerializer(Zoo.class, new ZooSerializer());
                context.addSerializers(serializers);
            }
        };
        mapper.registerModule(m);
        mapper.configure(Feature.INDENT_OUTPUT, true);

        Zoo zoo = new Zoo();
        List<Animal> animals = new ArrayList<Animal>();
        animals.add(new Dog("Spike", "mutt", "red"));
        animals.add(new Cat("Fluffy", "spider ring"));
        zoo.animals = animals;

        System.out.println(zoo);
        String json = mapper.writeValueAsString(zoo);
        System.out.println(json);
    }

    static class Zoo {
        public Collection<Animal> animals = Collections.EMPTY_SET;

        public String toString() {
            StringBuilder sb = new StringBuilder();
            sb.append("Zoo: { ");
            for (Animal animal : animals)
                sb.append(animal.toString()).append(" , ");
            return sb.toString();
        }
    }

    static class ZooSerializer extends JsonSerializer<Zoo> {
        @Override
        public void serialize(Zoo t, JsonGenerator jg, SerializerProvider sp) throws IOException, JsonProcessingException {
            Map<Object, Animal> animalMap = new HashMap<Object, Animal>();
            for (Animal a : t.animals)
                animalMap.put(a.getName(), a);
            jg.writeObject(animalMap);
        }
    }

    @JsonTypeInfo(
            use=Id.NAME,
            include=As.PROPERTY,
            property="type")
    @JsonSubTypes({
        @Type(value=Cat.class,name="cat"),
        @Type(value=Dog.class,name="dog")
    })
    static abstract class AnimalMixIn {
    }

    static interface Animal<T> {
        T getName();
    }

    static abstract class AbstractAnimal<T> implements Animal<T> {
        private final T name;

        protected AbstractAnimal(T name) {
            this.name = name;
        }

        public T getName() {
            return name;
        }
    }

    static class Dog extends AbstractAnimal<String> {
        private final String breed;
        private final String leashColor;

        @JsonCreator
        public Dog(@JsonProperty("name") String name, @JsonProperty("breed") String breed,
                   @JsonProperty("leashColor") String leashColor)
        {
            super(name);
            this.breed = breed;
            this.leashColor = leashColor;
        }

        public String getBreed() {
            return breed;
        }

        public String getLeashColor() {
            return leashColor;
        }

        @Override
        public String toString() {
            return "Dog{" + "name=" + getName() + ", breed=" + breed + ", leashColor=" + leashColor + "}";
        }
    }

    static class Cat extends AbstractAnimal<String> {
        private final String favoriteToy;

        @JsonCreator
        public Cat(@JsonProperty("name") String name, @JsonProperty("favoriteToy") String favoriteToy) {
            super(name);
            this.favoriteToy = favoriteToy;
        }

        public String getFavoriteToy() {
            return favoriteToy;
        }

        @Override
        public String toString() {
            return "Cat{" + "name=" + getName() + ", favoriteToy=" + favoriteToy + '}';
        }
    }
}

编辑:添加可能澄清问题的其他测试用例

在阅读了有关类似问题的更多问题后,我决定尝试修改自定义序列化程序以缩小问题所在。我发现将Animal对象添加到任何通用类型的集合(List<Animal>Map<Object, Animal>已经过测试),类型信息未被序列化。但是,在序列化Animal[]时,包含了类型信息。不幸的是,虽然我可以在测试代码中改变这种行为,但我需要使用生产代码来序列化具有多态值的Map

将自定义ZooSerializer.serialize()方法更改为以下输出类型信息,但丢失了我需要的Map语义:

public void serialize(...) {
    Animal[] animals = t.animals.toArray(new Animal[0]);
    jg.writeObject(animals);
}

2 个答案:

答案 0 :(得分:3)

我找到了解决办法,或者这可能是合适的解决方案。无论哪种方式,它似乎都有效。如果有更好的方法,请告诉我。 (我觉得应该有)

我定义了一个实现Map<Object, Animal>的内部类,并将该类的实例提供给JsonGenerator.writeObject(),而不是提供Map。一旦通用声明被“隐藏”,Jackson似乎能够解析键和值类型,并为创建的TypeSerializer提供非空MapSerializer,从而产生所需的JSON输出。

以下对测试源代码的添加/修改会生成所需的输出。

private static class AnimalMap implements Map<Object, Animal> {
    private final Map<Object, Animal> map;

    public AnimalMap() {
        super();
        this.map = new HashMap<Object, Animal>();
    }

    // omitting delegation of all Map<> interface methods to this.map
}

static class ZooSerializer extends SerializerBase<Zoo> {
    public ZooSerializer() {
        super(Zoo.class);
    }

    @Override
    public void serialize(Zoo t, JsonGenerator jg, SerializerProvider sp) throws IOException, JsonProcessing Exception {
        AnimalMap animals = new AnimalMap();
        for (Animal a : t.animals)
            animals.put(a.getName(), a);
        jg.writeObject(animals);
    }
}

答案 1 :(得分:1)

处理类型信息时,JsonSerializerJsonDeserializer都使用替代方法。因此,您可能希望查看JsonSerializer.serializeWithType(...)和/或JsonDeserializer.deserializeWithType(...)。这些负责处理如何处理类型标识符的细节;通常委托实际工作的TypeSerializerTypeDeserializer,但需要更多关于串行器/解串器将使用的实际JSON结构的信息(大多数POJO的JSON对象,列表的JSON数组,JSON字符串)对于Java字符串等等 - 但所有这些都可以通过自定义序列化器/反序列化器进行配置。