2013-07-01 2 views
34

Questa è una domanda duplicato perché le seguenti domande sono o disordinato o non si risponde a tutti:Jackson non è davvero in grado di deserializzare JSON in un tipo generico?

deserializing-a-generic-type-with-jackson

jackson-deserialize-into-runtime-specified-class

jackson-deserialize-using-generic-class

jackson-deserialize-generic-class-variable

Spero che questo la domanda troverà finalmente una risposta che lo chiarisca definitivamente.

Avere un modello:

public class AgentResponse<T> { 

    private T result; 

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

ingresso JSON:

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

o

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

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

e due modi di deserializzazione tipi generici consigliato

Jackson non è mai in grado di gestire il tipo generico T, si tratta di una mappa da JavaType, ma trova argomento di tipo Object constructor a causa della cancellazione di 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<java.lang.String,java.lang.Integer>>]: 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) 
+0

Si dovrebbe postare quella domanda sulla mailing list di jackson-users – fge

+0

Inoltre, si tenta veramente di deserializzare _all_ quel JSON o solo il valore membro 'result' di questo JSON? – fge

+0

Sto deserializzando l'input JSON in AgentResponse. Non so come e perché lo avrei deserializzato parzialmente. È solo una risposta con lo stato e il valore del risultato, che può essere Oggetto, raccolta, mappa, ecc. Ho modificato la domanda quindi c'è solo il risultato generico, – lisak

risposta

49

È necessario aggiungere alcune annotazioni sul costruttore per dire a Jackson come costruire l'oggetto. Di seguito ha lavorato per me:

public class AgentResponse<T> { 

    private T result; 

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

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

+3

L'uomo che hai salvato la mia giornata, stavo eseguendo il debug del jackson fino in fondo e ho visto che ha trovato gli argomenti del costruttore, quindi ho pensato che fosse il problema generico. In effetti, il vero problema era che Jackson non poteva sapere i nomi degli argomenti del costruttore che JVM non lo fornisce. Accidenti dovrei provarlo senza generici prima ... Grazie! – lisak

+4

E se non si è in grado o non si vuole annotare direttamente la classe, è possibile annotare un mixin invece: http://wiki.fasterxml.com/JacksonMixInAnnotations – dnault

4

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

Questa è la mia classe del modello

public class BasicMessage<T extends Serializable> implements Message<T> { 
    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 la deserializzazione il carico utile

public static <T extends Serializable> BasicMessage<T> getConcreteMessageType(String jsonString, Class<T> 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 il BasicMessageObject in una stringa.

+0

+1 ... si perderebbe comunque l'immutabilità dell'oggetto creato. Oppure è possibile anche con i setter privati? – Karussell

1

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

Supponiamo che T possa essere una classe che estende la classe astratta Result.

public class AgentResponse<T extends Result> { 
    public Hits<T> 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 ogni della classe (o la loro supertipo comune) che può essere passato come parametro T è annotato, Jackson includerà informazioni sui parametri T in JSON. Tale JSON può quindi essere deserializzato senza conoscere il parametro T in fase di compilazione.
Questo Jackson documentation link parla della deserializzazione polimorfica ma è utile fare riferimento anche a questa domanda.