48

This is a duplicate question because the following questions are either messy or they are not answered at all:

deserializing-a-generic-type-with-jackson

jackson-deserialize-into-runtime-specified-class

jackson-deserialize-using-generic-class

jackson-deserialize-generic-class-variable

I hope that this question will finally find an answer that makes this clear for good.

Having a model :

public class AgentResponse<T> {

    private T result;

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

JSON input:

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

and two recommended ways of deserializing generic types :

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

or

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

Jackson is never able to deal with the generic type T, it figures it's a Map from JavaType, but it finds Object type constructor argument because of type erasure and throws an error. So is this a Jackson bug, or am I doing something wrong? What else is explicit specification of TypeReference or JavaType for?

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: 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)
Community
  • 1
  • 1
lisak
  • 21,611
  • 40
  • 152
  • 243
  • You should post that question on the jackson-users mailing list – fge Jul 01 '13 at 09:20
  • Also, do you really try and deserialize _all_ that JSON or just this JSON's `result` member value? – fge Jul 01 '13 at 09:22
  • I'm deserializing the JSON input into AgentResponse. I don't know how and why would I deserialize it partially. It's just a response with status and result value, that may be Object, collection, map, etc. I modified the question so there is just the generic result, – lisak Jul 01 '13 at 09:25

6 Answers6

57

You need to add some annotations on the constructor to tell Jackson how to build the object. The following worked for me:

public class AgentResponse<T> {

    private T result;

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

Without the @JsonCreator annotation, Jackson cannot know to call this constructor. And without the @JsonProperty annotation, Jackson does not know that the first argument of the constructor maps to the result property.

cambecc
  • 4,083
  • 1
  • 23
  • 24
  • 3
    Man you saved my day, I was debugging the jackson stack all the way down and I saw it found the constructor arguments, so I thought it was the generic problem. In fact the real problem was that jackson couldn't know the constructor argument names cause JVM doesn't provide it. Damn I should have try that without generics first... Thanks ! – lisak Jul 01 '13 at 12:44
  • 4
    And if you are unable or unwilling to annotate the class directly, you can annotate a mixin instead: http://wiki.fasterxml.com/JacksonMixInAnnotations – dnault Sep 12 '13 at 18:18
  • Just a side point: if you have many parameters in the constructor you have to annotate all of them with @JsonProperty – Izerlotti Mar 04 '20 at 07:37
  • is there any special code require to perform dezerialise ? or standard jackson derserialisation will work on the above approach ? I have seen people writing special derserialisation code with type reference, hence the question. – Harshit Feb 26 '21 at 05:21
9

I tried using the same approach but I haven't annotated my model class. It worked fine for me.

This is my model class

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

And I used the following method for deserializing the payload

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

        }
 }

where jsonString contains the BasicMessageObject in a string.

yaswanth
  • 2,349
  • 1
  • 23
  • 33
3

If you programmatically pick up the java.lang.reflect.Type from for instance a method return type or a field, then it is easiest to use

Type type = obj.getClass().getDeclaredField( "aKnownFieldName" ).getGenericType(); 
// or
Type type = obj.getClass().getDeclaredMethod( "aKnownMethod" ).getGenericReturnType(); 


ObjectMapper mapper = new ObjectMapper();
JavaType javaType = mapper.getTypeFactory().constructType( type );
Object value = mapper.readValue( json, javaType );

A fully nested JavaType is created, so Controller<PID<Temperature,Double>>> will be deserialzed correctly.

Niclas Hedhman
  • 584
  • 4
  • 14
1

JSON string that needs to be deserialized will have to contain the type information about parameter T.
You will have to put Jackson annotations on every class that can be passed as parameter T to class AgentResponse so that the type information about parameter type T can be read from / written to JSON string by Jackson.

Let us assume that T can be any class that extends abstract class 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 {

}

Once each of the class (or their common supertype) that can be passed as parameter T is annotated, Jackson will include information about parameter T in the JSON. Such JSON can then be deserialized without knowing the parameter T at compile time.
This Jackson documentation link talks about Polymorphic Deserialization but is useful to refer to for this question as well.

Sanjeev Sachdev
  • 1,241
  • 1
  • 15
  • 23
0
public class AgentResponse<T> {

private T result;

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

}

So for the above class structure and T can be of Type T1,T2 class

So to deserialize for AgentResponse, use the following code

JavaType javaType = objectMapper.getTypeFactory.constructParametricType(AgentResponse.class,T1.class)
AgentResponse<T1> agentResponseT1 = objectMapper.readValue(inputJson,javaType);
jily
  • 344
  • 2
  • 11
0

Solution :

Use TypeReference instead of class

 public T getObject(String json, TypeReference typeReference) throws JsonParseException, JsonMappingException, IOException{
      ObjectMapper mapper = new ObjectMapper();
      mapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
     T t = (T) mapper.readValue(json, typeReference);
      return t;
   }

My Pojo Structure

    public class SQSRequest<T> implements Serializable {    
    private T data;
    private String msgId;
    private String msgGroupId;
    .... 
}


        
 public class EmailDetails implements Serializable {
     private Map<String,String> paramMap;
        .... 
} 

Use

SQSRequest<EmailDetails> req2=this.getObject(str, new TypeReference<SQSRequest<EmailDetails>>() {});