property jsonproperty baeldung java json jackson json-deserialization

java - baeldung - jsonproperty c#



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

Puedo serializar y deserializar una jerarquía de clases donde la clase base abstracta está anotada con

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

pero no @JsonSubTypes enumera las subclases, y las subclases en sí mismas están relativamente unánimes, teniendo solo @JsonCreator en el constructor. ObjectMapper es vanilla, y no estoy usando mixin.

La documentación de Jackson sobre Deserialización polimórfica e "ID de tipo" sugiere (fuertemente) Necesito la anotación @JsonSubTypes en la clase base abstracta, o usarla en una mezcla, o que necesito registrar los subtipos con ObjectMapper . Y hay muchas preguntas SO y / o publicaciones en blogs que concuerdan. 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 el comportamiento no documentado (que puede cambiar) o está sucediendo algo más? (Pregunto porque realmente no quiero que sea ninguno de los dos últimos. Pero tengo que saberlo ).

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 en el mismo contenedor 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 esto:

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

Las subclases SubB y SubC son iguales excepto que el campo a se declara String (no int ) en SubB y boolean (no int ) en SubC (y el método getA 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 se rompe en la segunda línea de try { , puede inspeccionar actual1 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() ).


Hay dos formas de lograr polimorfismo en serialización y deserialización con Jackson. Se definen en la Sección 1. Uso en el link que publicó.

Tu codigo

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

es un ejemplo del segundo enfoque. Lo primero que hay que notar es que

Todas las instancias de tipo anotado y sus subtipos usan estas configuraciones (a menos que sean anuladas por otra anotación)

Entonces, este valor de configuración se propaga a todos los subtipos. Entonces, necesitamos un identificador de tipo que asigne un tipo de Java a un valor de texto en la cadena JSON y viceversa. En su ejemplo, esto viene dado por JsonTypeInfo.Id#MINIMAL_CLASS

Significa que el nombre de la clase Java con una ruta mínima se usa como el identificador de tipo.

Por lo tanto, se genera un nombre de clase mínimo a partir de la instancia de destino y se escribe en el contenido JSON al serializarlo. O un nombre de clase mínimo se usa para determinar el tipo de objetivo para la deserialización.

También podría haber usado JsonTypeInfo.Id#NAME que

Significa que el nombre de tipo lógico se usa como información de tipo; nombre tendrá que resolverse por separado a tipo de concreto real ( Class ).

Para proporcionar un nombre de tipo lógico, use @JsonSubTypes

Anotación utilizada con JsonTypeInfo para indicar subtipos de tipos polimórficos serializables y para asociar nombres lógicos utilizados en contenido JSON (que es más portátil que el uso de nombres de clase de Java físicos).

Esta es solo otra forma de lograr el mismo resultado. La documentación que está preguntando acerca de los estados

Los ID de tipo que se basan en el nombre de la clase Java son bastante sencillos: es solo un nombre de clase, posiblemente una eliminación de prefijo simple (para la variante "mínima"). Pero el nombre del tipo es diferente: uno tiene que tener un mapeo entre el nombre lógico y la clase real.

Por lo tanto, los diversos valores JsonTypeInfo.Id que se relacionan con los nombres de las clases son sencillos porque se pueden generar automáticamente. Sin embargo, para los nombres de tipo, debe proporcionar el valor del mapeo explícitamente.