Jackson non è davvero in grado di deserializzare JSON in un tipo generico?

Questa è una domanda doppia perché le seguenti domande sono confuse o non ricevono alcuna risposta:

deserializzazione-a-generico-type-con-jackson

jackson-deserialize-in-runtime-specificato di class

jackson-deserialize-con-generic-class

jackson-deserialize-generic-class-variabile

Spero che questa domanda trovi finalmente una risposta che lo chiarisca definitivamente.

Avere un modello:

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

Input JSON:

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

e due metodi consigliati per deserializzare i tipi generici:

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

o

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

Jackson non è mai in grado di gestire il tipo generico T, si tratta di una mappa di JavaType, ma trova l’argomento del costruttore di tipo Object a causa della cancellazione del tipo e genera un errore. Quindi questo è un bug di Jackson, o sto facendo qualcosa di sbagliato? Cos’altro sono le specifiche esplicite di TypeReference o JavaType?

 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: [email protected]; 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) 

È necessario aggiungere alcune annotazioni sul costruttore per dire a Jackson come build l’object. Quanto segue ha funzionato per me:

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

Senza l’annotazione @JsonCreator , Jackson non può sapere di chiamare questo costruttore. E senza l’annotazione @JsonProperty , Jackson non sa che il primo argomento del costruttore mappa la proprietà result .

Ho provato ad utilizzare lo stesso approccio ma non ho annotato la mia class del modello. Ha funzionato bene per me.

Questa è la mia class di modello

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

E ho usato il seguente metodo per deserializzare il carico utile

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

dove jsonString contiene BasicMessageObject in una stringa.

La stringa JSON che deve essere deserializzata dovrà contenere le informazioni sul tipo del parametro T
Dovrai mettere le annotazioni di Jackson su ogni class che può essere passata come parametro T alla class AgentResponse modo che le informazioni sul tipo di parametro T possano essere lette da / scritte nella stringa JSON da Jackson.

Supponiamo che T possa essere una qualsiasi class che estende la class astratta 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 volta che ciascuna class (o il suo supertipo comune) che può essere passato come parametro T è annotata, Jackson includerà informazioni sul parametro T nel JSON. Tale JSON può quindi essere deserializzato senza conoscere il parametro T al momento della compilazione.
Questo link alla documentazione di Jackson parla della deserializzazione polimorfica, ma è utile fare riferimento anche a questa domanda.