Deserializar tipos polimórficos con Jackson

Si tengo una estructura de clase así:

public abstract class Parent { private Long id; ... } public class SubClassA extends Parent { private String stringA; private Integer intA; ... } public class SubClassB extends Parent { private String stringB; private Integer intB; ... } 

¿Hay una forma alternativa de deserializar diferente de @JsonTypeInfo ? Usando esta anotación en mi clase principal:

 @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "objectType") 

Preferiría no tener que forzar a los clientes de mi API a incluir "objectType": "SubClassA" para deserializar una subclase principal.

En lugar de utilizar @JsonTypeInfo , ¿Jackson proporciona una forma de anotar una subclase y distinguirla de otras subclases a través de una propiedad única? En mi ejemplo anterior, esto sería algo así como, “Si un objeto JSON tiene "stringA": ... deserializarlo como SubClassA , si tiene "stringB": ... deserializarlo como SubClassB “.

@JsonTypeInfo algo como @JsonTypeInfo y @JsonSubTypes , pero he elegido los documentos y parece que ninguna de las propiedades que se pueden suministrar parece coincidir con lo que describes.

Podría escribir un deserializador personalizado que utilice las @JsonSubTypes de “nombre” y “valor” de @JsonSubTypes forma no estándar para lograr lo que desea. El deserializador y @JsonSubTypes se suministrarían en su clase base y el deserializador usaría los valores de “nombre” para verificar la presencia de una propiedad y, si existe, entonces deserializará el JSON en la clase proporcionada en la propiedad “valor”. Tus clases se verían así:

 @JsonDeserialize(using = PropertyPresentDeserializer.class) @JsonSubTypes({ @Type(name = "stringA", value = SubClassA.class), @Type(name = "stringB", value = SubClassB.class) }) public abstract class Parent { private Long id; ... } public class SubClassA extends Parent { private String stringA; private Integer intA; ... } public class SubClassB extends Parent { private String stringB; private Integer intB; ... } 

No. Tal característica ha sido solicitada, podría llamarse “inferencia de tipo” o “tipo implícito”, pero nadie ha presentado una propuesta general viable sobre cómo debería funcionar esto todavía. Es fácil pensar en formas de respaldar soluciones específicas para casos específicos, pero encontrar una solución general es más difícil.

Mi aplicación requiere que preserve la estructura anterior, así que encontré una forma de soportar el polymorphism sin cambiar los datos. Esto es lo que hago:

  1. Extender JsonDeserializer
  2. Convierte a árbol y lee el campo, luego regresa el objeto Subclase

     @Override public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { JsonNode jsonNode = p.readValueAsTree(); Iterator> ite = jsonNode.fields(); boolean isSubclass = false; while (ite.hasNext()) { Map.Entry entry = ite.next(); // **Check if it contains field name unique to subclass** if (entry.getKey().equalsIgnoreCase("Field-Unique-to-Subclass")) { isSubclass = true; break; } } if (isSubclass) { return mapper.treeToValue(jsonNode, SubClass.class); } else { // process other classes } } 

Aquí hay una solución que se me ocurrió que se expande un poco en la de Erik Gillespie. Hace exactamente lo que pediste y funcionó para mí.

Usando Jackson 2.9

 @JsonDeserialize(using = CustomDeserializer.class) public abstract class BaseClass { private String commonProp; } // Important to override the base class' usage of CustomDeserializer which produces an infinite loop @JsonDeserialize(using = JsonDeserializer.None.class) public class ClassA extends BaseClass { private String classAProp; } @JsonDeserialize(using = JsonDeserializer.None.class) public class ClassB extends BaseClass { private String classBProp; } public class CustomDeserializer extends StdDeserializer { protected CustomDeserializer() { super(BaseClass.class); } @Override public BaseClass deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException { TreeNode node = p.readValueAsTree(); // Select the concrete class based on the existence of a property if (node.get("classAProp") != null) { return p.getCodec().treeToValue(node, ClassA.class); } return p.getCodec().treeToValue(node, ClassB.class); } } // Example usage String json = ... ObjectMapper mapper = ... BaseClass instance = mapper.readValue(json, BaseClass.class); 

Si quiere ser más elegante, puede expandir CustomDeserializer para incluir un Map> que mapee un nombre de propiedad que, cuando CustomDeserializer presente, se correlaciona con una clase específica. Tal enfoque se presenta en este artículo .

Por cierto, hay un problema github solicitando esto aquí: https://github.com/FasterXML/jackson-databind/issues/1627