【问题标题】:Using Jackson's @JsonTypeInfo with a custom serializer将 Jackson 的 @JsonTypeInfo 与自定义序列化程序一起使用
【发布时间】:2011-11-15 17:41:19
【问题描述】:

我遇到了 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&lt;Animal&gt;Map&lt;Object, Animal&gt; 已测试),类型信息未序列化。但是,在序列化 Animal[] 时,会包含类型信息。不幸的是,虽然我可以在测试代码中改变这种行为,但我需要我的生产代码来序列化具有多态值的Map

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

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

【问题讨论】:

  • 我同意自定义序列化程序中的JsonGenerator 使用AnimalMixIn 配置似乎是合理的。希望 StaxMan 能加入进来。
  • 大量从您的博客中借用,添加了一些内容以使其更接近我的实际代码。感谢您的示例,它帮助我入门!
  • Java 集合的问题是类型擦除,在处理根值(而不是属性值,保留类型)时。数组保留内容类型(因为它不是使用泛型完成的)。

标签: java json jackson


【解决方案1】:

我找到了解决方法,或者这可能是合适的解决方案。无论哪种方式,它似乎都在工作。请让我知道是否有更好的方法。 (我觉得应该有)

我定义了一个实现Map&lt;Object, Animal&gt; 的内部类,并将该类的实例提供给JsonGenerator.writeObject(),而不是提供Map。一旦通用声明被“隐藏”并为创建的MapSerializer 提供非空TypeSerializer,Jackson 似乎能够解析键和值类型,从而产生所需的 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,它可以表达泛型类型签名。
【解决方案2】:

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

【讨论】:

  • 我对如何从我当前的代码中调用serializeWithType() 方法感到有些困惑。我的假设是当我调用JsonGenerator.writeObject(Map&lt;Object, Animal&gt;) 时映射序列化程序会调用它,但似乎并非如此。我应该在serialize() 方法中调用writeObject() 以外的东西吗?
  • 我是否应该使用Serializers 的自定义实例将我的自定义Serializer 注册到ObjectMapper 而不是SimplerSerializers
  • 更新问题。如果我尝试在自定义序列化程序中序列化 Animal[],则类型写入正确。将Animal 对象放入任何类型的泛型集合时,该类型会被忽略。我如何向 MapSerializer 或其他 Collection 序列化程序表明它们的键和/或值是多态类型并且应该使用 serializeWithType() 而不是 serialize() 进行序列化?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-08-02
  • 1970-01-01
  • 2011-11-01
  • 1970-01-01
  • 2019-08-10
  • 2016-01-11
  • 2013-10-10
相关资源
最近更新 更多