Использование @JsonTypeInfo Джексона с пользовательским сериализатором - PullRequest
1 голос
/ 15 ноября 2011

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

3 голосов
/ 17 ноября 2011

Я нашел обходной путь, или, может быть, это подходящее решение.В любом случае, похоже, работает.Пожалуйста, дайте мне знать, если есть лучший способ.(Я чувствую, что должно быть)

Я определил внутренний класс, который реализует Map<Object, Animal> и предоставил экземпляр этого класса для JsonGenerator.writeObject() вместо предоставления ему Map.Похоже, что Джексон может разрешить типы ключей и значений, как только общие объявления «скрыты» и предоставляют ненулевое 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 голос
/ 16 ноября 2011

При работе с информацией о типах и JsonSerializer, и JsonDeserializer используют альтернативные методы. Так что вы, вероятно, хотите взглянуть на JsonSerializer.serializeWithType(...) и / или JsonDeserializer.deserializeWithType(...). Они заботятся о деталях обработки идентификатора типа; обычно путем делегирования фактическим TypeSerializer и TypeDeserializer, которые выполняют работу, но нуждаются в дополнительной информации о фактической структуре JSON, которую будет использовать сериализатор / десериализатор (объект JSON для большинства POJO, массивы JSON для списков, строки JSON для строк Java и и так далее, но все это настраивается с помощью пользовательских сериализаторов / десериализаторов).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...