O @JsonSubTypes de Jackson ainda é necessário para a desserialização polimórfica?

Sou capaz de serializar e desserializar uma hierarquia de classes em que a classe base abstrata é anotada com

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

mas não@JsonSubTypes listar as subclasses e as próprias subclasses são relativamente não anotadas, tendo apenas um@JsonCreator no construtor. O ObjectMapper é baunilha e não estou usando um mixin.

Documentação de JacksonDeserialização polimórfica e "IDs de tipo" sugere (fortemente) eu preciso do@JsonSubTypes anotação na classe base abstrata, ou use-a em um mixin, ou que eu precisoregistrar os subtipos no ObjectMapper. E há muitas perguntas sobre SO e / ou postagens de blog que concordam. No entanto, funciona. (Esse é o Jackson 2.6.0.)

Então ... eu sou o beneficiário de um recurso ainda não documentado ou estou contando com um comportamento não documentado (que pode mudar) ou algo mais está acontecendo? (Estou perguntando porque realmente não quero que seja um dos dois últimos. MasEu tenho que saber.)

EDIT: Adicionando código - e um comentário. O comentário é: eu deveria ter mencionado que todas as subclasses que estou desserializando estão no mesmo pacote e no mesmo jarro da classe abstrata base.

Classe base abstrata:

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);
}

Uma subclasse dele:

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();
    }
}

SubclassesSubB eSubC são os mesmos, exceto que o campoa é declaradoString (nãoint) noSubB eboolean (nãoint) noSubC (e o métodogetA é modificado de acordo).

Classe de teste:

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);
    }
}

Este teste passa e se você quebrar no segundotry { linha que você pode inspecionaractual1 e veja:

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

Portanto, as três subclasses foram serializadas corretamente (cada uma com seu nome de classe como id) e depois desserializadas, e o resultado comparado com igual (cada subclasse possui um "tipo de valor"equals())

questionAnswers(1)

yourAnswerToTheQuestion