Jackson is not deserialising a generic list that it has serialised

When using Apache Jersey with Jackson for JSON serialisation (on both server and client), I'm hitting a problem when deserialising a generic List.

The JSON I am producing is as follows, all 3 classes in "data" implement "CheckStatusDetail":

{
  "errorCode" : 0,
  "errorMessage" : null,
  "type" : "array",
  "data" : [ {
    "@class" : "com.rrr.base.status.module.dto.DiscoveryAgentCheckStatusDetail",
    "serverInfo" : {
      "@class" : "com.rrr.base.util.discovery.config.xml.XMLServerInfo",
      "name" : "java",
      "location" : "THEO",
      "description" : "sddgs",
      "group" : "java",
      "aliases" : [ "mercury" ]
    }
  }, {
    "@class" : "com.rrr.base.status.module.dto.MongoDBCheckStatusDetail",
    "addresses" : [ "localhost:27017" ],
    "version" : "2.5",
    "connected" : true
  }, {
    "@class" : "com.rrr.base.status.module.dto.NetworkCheckStatusDetail",
    "splitBrain" : false
  } ],
  "count" : 3,
  "status" : 0
}

The object that produces this JSON looks like this, I'm using the same class on the client side:

public class NSResponse<T> implements Serializable {

    private static final long serialVersionUID = 1L;

    public static final int STATUS_OK       = 0;
    public static final int STATUS_ERROR    = -1;

    public static final String TYPE_OBJECT  = "object";
    public static final String TYPE_ARRAY   = "array";

    private int status;
    private int errorCode;
    private String errorMessage;
    private String type;

    private List<T> data;

    private int count;

    public NSResponse() {   }

    public NSResponse(int errorCode, String errorMessage) {
        this.status = STATUS_ERROR;
        this.errorCode = errorCode;
        this.errorMessage = errorMessage;
    }

    public NSResponse(T data) {
        this.status = STATUS_OK;
        this.type = TYPE_OBJECT;
        this.data = new ArrayList<T>();
        this.data.add(data);
        this.count = this.data.size();
    }

    public NSResponse(List<T> data) {
        this.status = STATUS_OK;
        this.type = TYPE_ARRAY;
        this.data = data;
        this.count = (data == null) ? 0 : data.size();
    }

    /* Getters and setters omitted */
}

The @class information is being applied since I added this annotation to my CheckStatusDetail interface:

@JsonTypeInfo(use=JsonTypeInfo.Id.CLASS, include=JsonTypeInfo.As.PROPERTY, property="@class")
public interface CheckStatusDetail extends Serializable {}

When trying to consume the JSON at the client end, I get this error:

java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.rrr.base.status.module.dto.CheckStatusDetail

This error occurs the first time I try to access the "data" field after deserialising it. If I debug the client end, Jackson seems to be returning a List<LinkedHashMap>, which explains the error, since I'm expecting a List<CheckStatusDetail>.

What am I doing wrong?


You need to show bit more code, specifically on how you invoke deserialization, but from the error I would guess you are not passing parameterization of T. If it is missing, T can only be assumed to be of type Object, and nominal type of Object is bound to "native" Java type, which for JSON objects is Map (and specifically, LinkedHashMap to preserve order).

So you probably just need to specify generic type of object on deserialization (for serialization it is not needed as runtime type is available); either by using TypeReference (not plain Class, since that has no generic type info), or by constructing generic-enabled JavaType. For example:

NSResponse<CheckStatusDetail> resp = mapper.readValue(json, new TypeReference<NSResponse<CheckStatusDetail>>() { });

or

NSResponse<CheckStatusDetail> resp = mapper.readValue(json, TypeFactory.genericType(NSResponse.class, CheckStatusDetails.class));

both work; latter is necessary if type is only dynamically available.