Подтвердить что ты не робот

Является ли Jackson @JsonSubTypes еще необходимым для полиморфной десериализации?

Я могу сериализовать и десериализовать иерархию классов, где абстрактный базовый класс аннотируется с помощью

@JsonTypeInfo(
    use = JsonTypeInfo.Id.MINIMAL_CLASS,
    include = JsonTypeInfo.As.PROPERTY,
    property = "@class")

но не @JsonSubTypes, перечисляющий подклассы, а сами подклассы относительно unannotated, имея только @JsonCreator в конструкторе. ObjectMapper - ваниль, и я не использую mixin.

Документация Джексона на Полиморфная десериализация и "идентификаторы типов" предлагает (сильно) Мне нужна аннотация @JsonSubTypes в абстрактном базовом классе или использовать ее в mixin, или что мне нужно зарегистрировать подтипы с помощью ObjectMapper. И есть много вопросов SO и/или сообщений в блоге, которые согласны. Но это работает. (Это Джексон 2.6.0.)

Итак, я являюсь бенефициаром еще недокументированной функции или полагаюсь на недокументированное поведение (которое может измениться) или что-то еще происходит? (Я спрашиваю, потому что я действительно не хочу, чтобы он был одним из двух последних. Но Мне нужно знать.)

EDIT: добавление кода - и один комментарий. Комментарий: Я должен был упомянуть, что все подклассы, которые я десериализую, находятся в одном пакете и в той же самой бане, что и базовый абстрактный класс.

Абстрактный базовый класс:

package so;
import com.fasterxml.jackson.annotation.JsonTypeInfo;

@JsonTypeInfo(
    use = JsonTypeInfo.Id.MINIMAL_CLASS,
    include = JsonTypeInfo.As.PROPERTY,
    property = "@class")
public abstract class PolyBase
{
    public PolyBase() { }

    @Override
    public abstract boolean equals(Object obj);
}

Подкласс:

package so;
import org.apache.commons.lang3.builder.EqualsBuilder;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;

public final class SubA extends PolyBase
{
    private final int a;

    @JsonCreator
    public SubA(@JsonProperty("a") int a) { this.a = a; }

    public int getA() { return a; }

    @Override
    public boolean equals(Object obj) {
        if (null == obj) return false;
        if (this == obj) return true;
        if (this.getClass() != obj.getClass()) return false;

        SubA rhs = (SubA) obj;
        return new EqualsBuilder().append(this.a, rhs.a).isEquals();
    }
}

Подклассы SubB и SubC совпадают, за исключением того, что поле a объявлено String (не int) в SubB и boolean (not int) в SubC ( и метод getA изменяется соответственно).

Класс тестирования:

package so;    
import java.io.IOException;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.testng.annotations.Test;
import static org.assertj.core.api.Assertions.*;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;

public class TestPoly
{
    public static class TestClass
    {
        public PolyBase pb1, pb2, pb3;

        @JsonCreator
        public TestClass(@JsonProperty("pb1") PolyBase pb1,
                         @JsonProperty("pb2") PolyBase pb2,
                         @JsonProperty("pb3") PolyBase pb3)
        {
            this.pb1 = pb1;
            this.pb2 = pb2;
            this.pb3 = pb3;
        }

        @Override
        public boolean equals(Object obj) {
            if (null == obj) return false;
            if (this == obj) return true;
            if (this.getClass() != obj.getClass()) return false;

            TestClass rhs = (TestClass) obj;
            return new EqualsBuilder().append(pb1, rhs.pb1)
                                      .append(pb2, rhs.pb2)
                                      .append(pb3, rhs.pb3)
                                      .isEquals();
        }
    }

    @Test
    public void jackson_should_or_should_not_deserialize_without_JsonSubTypes() {

        // Arrange
        PolyBase pb1 = new SubA(5), pb2 = new SubB("foobar"), pb3 = new SubC(true);
        TestClass sut = new TestClass(pb1, pb2, pb3);

        ObjectMapper mapper = new ObjectMapper();

        // Act
        String actual1 = null;
        TestClass actual2 = null;

        try {
            actual1 = mapper.writeValueAsString(sut);
        } catch (IOException e) {
            fail("didn't serialize", e);
        }

        try {
            actual2 = mapper.readValue(actual1, TestClass.class);
        } catch (IOException e) {
            fail("didn't deserialize", e);
        }

        // Assert
        assertThat(actual2).isEqualTo(sut);
    }
}

Этот тест проходит, и если вы нарушаете вторую строку try {, вы можете проверить actual1 и увидеть:

{"pb1":{"@class":".SubA","a":5},
 "pb2":{"@class":".SubB","a":"foobar"},
 "pb3":{"@class":".SubC","a":true}}

Таким образом, три подкласса были правильно сериализованы (каждый со своим именем класса как id), а затем десериализованы, а результат сравним с равным (каждый подкласс имеет "тип значения" equals()).

4b9b3361

Ответ 1

Существует два способа достижения полиморфизма в сериализации и десериализации с Джексоном. Они определены в разделе Раздел 1. Использование в ссылке, которую вы опубликовали.

Ваш код

@JsonTypeInfo(
    use = JsonTypeInfo.Id.MINIMAL_CLASS,
    include = JsonTypeInfo.As.PROPERTY,
    property = "@class")

является примером второго подхода. Прежде всего следует отметить, что

Все экземпляры аннотированного типа и его подтипы используют эти параметры (если не переопределяется другой аннотацией)

Итак, это значение конфигурации распространяется на все подтипы. Затем нам нужен идентификатор типа, который отобразит тип Java в текстовое значение в строке JSON и наоборот. В вашем примере это дается JsonTypeInfo.Id#MINIMAL_CLASS

означает, что имя класса Java с минимальным путем используется как идентификатор типа.

Таким образом, минимальное имя класса генерируется из целевого экземпляра и записывается в содержимое JSON при сериализации. Или минимальное имя класса используется для определения целевого типа для десериализации.

Вы могли бы также использовать JsonTypeInfo.Id#NAME, который

означает, что имя логического типа используется как информация о типе; имя будет затем необходимо отдельно разделить конкретный конкретный тип (Class).

Чтобы предоставить такое имя логического типа, вы используете @JsonSubTypes

Аннотации, используемые с JsonTypeInfo для обозначения подтипов сериализуемые полиморфные типы и связывать логические именав контенте JSON (который более переносим, ​​чем использование физической Java имена классов).

Это просто еще один способ добиться того же результата. Документация, которую вы запрашиваете о состоянии

Идентификаторы типов, основанные на имени класса Java, справедливы прямолинейный: это просто имя класса, возможно, простой простой префикс удаление (для "минимального" варианта). Но имя типа отличается: у одного есть иметь сопоставление между логическим именем и фактическим классом.

Таким образом, различные значения JsonTypeInfo.Id, относящиеся к именам классов, являются прямыми, поскольку они могут быть автоматически сгенерированы. Однако для имен типов вам нужно явно указать значение отображения.