Jackson не десериализует общий список, который он сериализовал

#java #json #serialization #jersey #jackson

#java #json #сериализация #джерси #джексон

Вопрос:

При использовании Apache Jersey с Jackson для сериализации JSON (как на сервере, так и на клиенте), я сталкиваюсь с проблемой при десериализации общего списка.

JSON, который я создаю, выглядит следующим образом: все 3 класса в «data» реализуют «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
}
  

Объект, который создает этот JSON, выглядит следующим образом, я использую тот же класс на стороне клиента:

 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 */
}
  

Информация @class применяется с тех пор, как я добавил эту аннотацию в свой интерфейс CheckStatusDetail:

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

При попытке использовать JSON на стороне клиента я получаю эту ошибку:

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

Эта ошибка возникает при первой попытке получить доступ к полю «данные» после его десериализации. Если я отлаживаю клиентскую часть, Jackson, похоже, возвращает список<LinkedHashMap>, что объясняет ошибку, поскольку я ожидаю список<CheckStatusDetail>.

Что я делаю не так?

Ответ №1:

Вам нужно показать немного больше кода, в частности, о том, как вы вызываете десериализацию, но из ошибки я бы предположил, что вы не передаете параметризацию T. Если он отсутствует, можно предположить, что T имеет только тип Object, а номинальный тип объекта привязан к «родному» типу Java, который для объектов JSON является Map (и, в частности, LinkedHashMap для сохранения порядка).

Поэтому вам, вероятно, просто нужно указать общий тип объекта при десериализации (для сериализации это не требуется, поскольку доступен тип среды выполнения); либо с помощью TypeReference (не обычный класс, поскольку в нем нет информации об общем типе), либо путем построения JavaType с поддержкой общего типа. Например:

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

или

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

оба варианта работают; последний необходим, если type доступен только динамически.

Комментарии:

1. Отлично, мне пришлось использовать TypeFactory немного по-другому: «TypeFactory.type(NSResponse.class , CheckStatusDetails.class )», но, вероятно, несоответствие версий, и результат — это именно то, что я хотел. Спасибо!

2. Да, я не проверял точное название метода (плюс в версии 1.8 есть незначительные изменения), но важно то, что у вас это работает. 🙂

3. @seanhodges TypeFactory.type устарел, что использовать?

4. TypeFactory НЕ является устаревшим, только его статические методы. Итак, вам нужно найти экземпляр; чаще всего его находят с помощью ObjectMapper.getTypeFactory().

5. Вау, это решение было действительно отличным для меня! Это помогло мне в моем Java-приложении иметь возможность динамически обрабатывать данные массива JSON, полученные из Facebook Graph API, и десериализовать данные в массив соответствующего объекта.