¿Sigue siendo necesario el @JsonSubTypes de Jackson para la deserialización polimórfica?

Puedo serializar y deserializar una jerarquía de clases donde la clase base abstracta se anota con

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

pero no@JsonSubTypes enumerando las subclases, y las subclases en sí están relativamente sin anotar, teniendo solo un@JsonCreator en el constructor. El ObjectMapper es vainilla, y no estoy usando un mixin.

Documentación de Jackson sobreDeserialización polimórfica y "identificadores de tipo" sugiere (fuertemente) necesito el@JsonSubTypes anotación en la clase base abstracta, o usarla en un mixin, o que necesitoregistrar los subtipos con ObjectMapper. Y hay muchas preguntas SO y / o publicaciones de blog que están de acuerdo. Sin embargo, funciona. (Este es Jackson 2.6.0.)

Entonces ... ¿soy el beneficiario de una característica aún no documentada o estoy confiando en un comportamiento indocumentado (que puede cambiar) o está sucediendo algo más? (Lo pregunto porque realmente no quiero que sea ninguno de los dos últimos. PeroTengo que saber.)

EDITAR: Agregar código, y un comentario. El comentario es: debería haber mencionado que todas las subclases que estoy deserializando están en el mismo paquete y el mismo frasco que la clase abstracta base.

Clase base abstracta:

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

Una subclase de ella:

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

SubclasesSubB ySubC son iguales excepto ese campoa se declaraString (noint) enSubB yboolean (noint) enSubC (y el métodogetA se modifica en consecuencia).

Clase de prueba:

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

Esta prueba pasa y si rompes en el segundotry { línea que puedes inspeccionaractual1 y ver:

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

Entonces, las tres subclases se serializaron correctamente (cada una con su nombre de clase como id) y luego se deserializaron, y el resultado se comparó igual (cada subclase tiene un "tipo de valor"equals())

Respuestas a la pregunta(1)

Su respuesta a la pregunta