【发布时间】: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<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 能加入进来。 -
大量从您的博客中借用,添加了一些内容以使其更接近我的实际代码。感谢您的示例,它帮助我入门!
-
Java 集合的问题是类型擦除,在处理根值(而不是属性值,保留类型)时。数组保留内容类型(因为它不是使用泛型完成的)。