¿Jackson realmente no puede deserializar a json en un tipo genérico?

Esta es una pregunta duplicada porque las siguientes preguntas son confusas o no se responden en absoluto:

deserializing-a-generic-type-with-jackson

jackson-deserialize-into-runtime-specified-class

jackson-deserialize-using-generic-class

jackson-deserialize-generic-class-variable

Espero que esta pregunta finalmente encuentre una respuesta que lo aclare para siempre.

Tener un modelo:

public class AgentResponse { private T result; public AgentResponse(T result) { this.result = result; } public T getResult() { return result; } } 

Entrada JSON:

 {"result":{"first-client-id":3,"test-mail-module":3,"third-client-id":3,"second-client-id":3}} 

y dos formas recomendadas de deserializar tipos generics:

 mapper.readValue(out, new TypeReference<AgentResponse<Map>>() {}); 

o

 JavaType javaType = mapper.getTypeFactory().constructParametricType(AgentResponse.class, Map.class); mapper.readValue(out, javaType); 

Jackson nunca puede tratar con el tipo genérico T, cree que es un Mapa de JavaType, pero encuentra un argumento de constructor de tipo Objeto debido a la borradura de tipo y arroja un error. Entonces, ¿es esto un error de Jackson, o estoy haciendo algo mal? ¿Qué otra cosa es la especificación explícita de TypeReference o JavaType para?

 com.fasterxml.jackson.databind.JsonMappingException: No suitable constructor found for type [simple type, class com.fg.mail.smtp.AgentResponse<java.util.Map>]: can not instantiate from JSON object (need to add/enable type information?) at [Source: java.io.InputStreamReader@4f2d26d; line: 1, column: 2] at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:164) at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:984) at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:276) at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:121) at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:2888) at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2064) 

Necesita agregar algunas anotaciones en el constructor para decirle a Jackson cómo construir el objeto. Lo siguiente funcionó para mí:

 public class AgentResponse { private T result; @JsonCreator public AgentResponse(@JsonProperty("result") T result) { this.result = result; } public T getResult() { return result; } } 

Sin la anotación @JsonCreator , Jackson no puede saber llamar a este constructor. Y sin la anotación @JsonProperty , Jackson no sabe que el primer argumento del constructor se correlaciona con la propiedad del result .

Intenté usar el mismo enfoque, pero no anoté mi clase de modelo. Funcionó bien para mí

Esta es mi clase de modelo

 public class BasicMessage implements Message { private MessageHeader messageHeader = new MessageHeader(); private T payload; public MessageHeader getHeaders() { return messageHeader; } public Object getHeader(String key) { return messageHeader.get(key); } public Object addHeader(String key, Object header) { return messageHeader.put(key, header); } public T getPayload() { return payload; } public void setPayload(T messageBody) { this.payload = messageBody; } } 

Y usé el siguiente método para deserializar la carga útil

 public static  BasicMessage getConcreteMessageType(String jsonString, Class classType) { try { ObjectMapper mapper = new ObjectMapper(); JavaType javaType = mapper.getTypeFactory().constructParametricType(BasicMessage.class, classType); return mapper.readValue(jsonString, javaType); } catch (IOException e) { } } 

donde jsonString contiene BasicMessageObject en una cadena.

La cadena JSON que necesita ser deserializada deberá contener la información de tipo sobre el parámetro T
Deberá colocar anotaciones de Jackson en cada clase que se pueda pasar como parámetro T para clasificar AgentResponse modo que la información de tipo sobre el tipo de parámetro T pueda leerse / escribirse en cadena JSON por Jackson.

Supongamos que T puede ser cualquier clase que extienda la clase abstracta Result .

 public class AgentResponse { public Hits hits; } @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT) @JsonSubTypes({ @JsonSubTypes.Type(value = ImageResult.class, name = "ImageResult"), @JsonSubTypes.Type(value = NewsResult.class, name = "NewsResult")}) public abstract class Result { } public class ImageResult extends Result { } public class NewsResult extends Result { } 

Una vez que cada clase (o su supertipo común) que se puede pasar como parámetro T se anota, Jackson incluirá información sobre el parámetro T en el JSON. Tal JSON puede deserializarse sin conocer el parámetro T en el momento de la comstackción.
Este enlace de documentación de Jackson habla de deserialización polimórfica, pero también es útil referirse a esta pregunta.