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

3
我在使用自定义序列化器时遇到了Jackson的问题,它不尊重@JsonTypeInfo注释。下面的简化示例不需要自定义序列化,并在不使用自定义序列化器时按预期输出"type"属性。但是,一旦启用了自定义序列化器,"type"属性就不会被写入,从而阻止反序列化。这是我的实际系统的测试用例,该系统需要自定义序列化器,并且如果有区别的话,正在尝试序列化Map>,其中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);
}

我同意在自定义序列化程序中的JsonGenerator使用AnimalMixIn配置似乎是合理的。希望StaxMan能够提供意见。 - Programmer Bruce
大部分内容借鉴了你的博客,稍微加入了一些东西以使其更接近我的实际代码。感谢你的示例,它帮助我入门了! - gordon_vt02
Java集合的问题在于类型擦除,当处理根值时(而不是属性值,因为类型保留)。数组保留内容类型(因为它不使用泛型实现)。 - StaxMan
2个回答

3
我找到了一个解决方法,或许这就是适当的解决方案。无论哪种情况,它似乎都能正常工作。如果有更好的方法,请告诉我。(我觉得应该有更好的方法)
我定义了一个内部类来实现Map,并向JsonGenerator.writeObject()提供了该类的实例,而不是提供一个Map。Jackson似乎能够在泛型声明“隐藏”后解析键和值类型,并为创建的MapSerializer提供一个非空的TypeSerializer,从而产生所需的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);
    }
}

当子类化Collection或Map时,泛型类型信息存储在超类型的类文件中(但不是类型本身!)。这是可能的解决方法之一 - 另一个是使用TypeReference / JavaType,它可以表示泛型类型签名。 - StaxMan

1

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


我对serializeWithType()方法在我的当前代码中如何被调用有些困惑。我的假设是当我调用JsonGenerator.writeObject(Map<Object, Animal>)时,地图序列化程序将调用它,但事实并非如此。在serialize()方法中,我应该调用除writeObject()之外的其他内容吗? - gordon_vt02
我应该使用自定义的Serializers实例来注册我的自定义SerializerObjectMapper中,而不是使用SimplerSerializers吗? - gordon_vt02
2
问题的更新。如果我尝试在我的自定义序列化器中序列化一个 Animal[],则类型会被正确写入。但是,当将 Animal 对象放入任何类型的通用集合中时,类型会被省略。我该如何指示 MapSerializer 或其他集合序列化器,它们的键和/或值是多态类型,并且应该使用 serializeWithType() 而不是 serialize() 进行序列化? - gordon_vt02

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接