У меня проблема с Джексоном, когда он не учитывает аннотации @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);
}