Невозможно десериализовать экземпляр java.util.ArrayList из токена START_OBJECT


129

Я пытаюсь опубликовать Listпользовательские объекты. Мой JSON в теле запроса таков:

{
    "collection": [
        {
            "name": "Test order1",
            "detail": "ahk ks"
        },
        {
            "name": "Test order2",
            "detail": "Fisteku"
        }
    ]
}

Код на стороне сервера, обрабатывающий запрос:

import java.util.Collection;

import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;


@Path(value = "/rest/corder")
public class COrderRestService {

    @POST
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes(MediaType.APPLICATION_JSON)
    public Response postOrder(Collection<COrder> orders) {
        StringBuilder stringBuilder = new StringBuilder();
        for (COrder c : orders) {
            stringBuilder.append(c.toString());
        }
        System.out.println(stringBuilder);
        return Response.ok(stringBuilder, MediaType.APPLICATION_JSON).build();
    }
}

Сущность COrder:

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class COrder {
    String name;
    String detail;

    @Override
    public String toString() {
        return "COrder [name=" + name + ", detail=" + detail
                + ", getClass()=" + getClass() + ", hashCode()=" + hashCode()
                + ", toString()=" + super.toString() + "]";
    }
}

Но возникает исключение:

SEVERE: Failed executing POST /rest/corder
org.jboss.resteasy.spi.ReaderException: org.codehaus.jackson.map.JsonMappingException: Can not deserialize instance of java.util.ArrayList out of START_OBJECT token
 at [Source: org.apache.catalina.connector.CoyoteInputStream@6de8c535; line: 1, column: 1]
    at org.jboss.resteasy.core.MessageBodyParameterInjector.inject(MessageBodyParameterInjector.java:183)
    at org.jboss.resteasy.core.MethodInjectorImpl.injectArguments(MethodInjectorImpl.java:88)
    at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:111)
    at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTarget(ResourceMethodInvoker.java:280)
    at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:234)
    at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:221)
    at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:356)
    at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:179)
    at org.jboss.resteasy.plugins.server.servlet.ServletContainerDispatcher.service(ServletContainerDispatcher.java:220)
    at org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:56)
    at org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:51)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:728)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:305)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:222)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:123)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:171)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:100)
    at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:953)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:408)
    at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1041)
    at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:603)
    at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:312)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
    at java.lang.Thread.run(Thread.java:724)

Ответы:


156

Проблема в JSON - по умолчанию он не может быть десериализован в Collectionмассив, потому что на самом деле это не массив JSON - это будет выглядеть так:

[
    {
        "name": "Test order1",
        "detail": "ahk ks"
    },
    {
        "name": "Test order2",
        "detail": "Fisteku"
    }
]

Поскольку вы не контролируете точный процесс десериализации (это делает RestEasy), первым вариантом было бы просто ввести JSON как a, Stringа затем взять под контроль процесс десериализации:

Collection<COrder> readValues = new ObjectMapper().readValue(
    jsonAsString, new TypeReference<Collection<COrder>>() { }
);

Вы бы немного потеряли удобство, не делая этого самостоятельно, но вы легко решите проблему.

Другой вариант - если вы не можете изменить JSON - было бы создать оболочку, соответствующую структуре вашего ввода JSON, и использовать ее вместо Collection<COrder>.

Надеюсь это поможет.


1
Отлично, я завернул в коллекцию, потому что в документации Resteasy был пример, но он был с XML.
isah

2
@isah nice, не могли бы вы поделиться этой рестайлинговой ссылкой, пожалуйста
nanospeck

подумать об этом. Я почти уверен, что у меня есть правильный код, и я отлаживаю его часами и по ходу меняю свои коды. Оказывается, мне просто не хватало квадратных скобок, чтобы указать, что мой пост представляет собой массив. Arrg. Думаю, это проклятие новичков, LOL. Всем, кто плохо знаком с JSON и Spring Data, не идите по моим стопам. :(
iamjoshua

Код не работает для меня, в контроллере какой должен быть ожидаемый код?
prem30488,

63

Вместо документа JSON вы можете обновить объект ObjectMapper, как показано ниже:

ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);

1
Спасибо, ответ мне помог
Хесус Санчес

Фантастика! Вы сэкономили день.
Эрве Мутомбо,

спасибо, пожалуйста, обратитесь на наш сайт mboot.herokuapp.com , мы публикуем статью, связанную с java - spring-boot
Салах Атва,

8

Это будет работать:

Проблема может возникнуть, когда вы пытаетесь прочитать список с одним элементом как JsonArray, а не JsonNode, или наоборот.

Поскольку вы не можете точно знать, содержит ли возвращаемый список один элемент (поэтому json выглядит так {...} ) или несколько элементов (а json выглядит так [{...}, {... }] ) - вам нужно будет проверить во время выполнения тип элемента.

Должно получиться так:

(Примечание: в этом примере кода я использую com.fasterxml.jackson)

String jsonStr = response.readEntity(String.class);
ObjectMapper mapper = new ObjectMapper();
JsonNode rootNode = mapper.readTree(jsonStr);

// Start by checking if this is a list -> the order is important here:                      
if (rootNode instanceof ArrayNode) {
    // Read the json as a list:
    myObjClass[] objects = mapper.readValue(rootNode.toString(), myObjClass[].class);
    ...
} else if (rootNode instanceof JsonNode) {
    // Read the json as a single object:
    myObjClass object = mapper.readValue(rootNode.toString(), myObjClass.class);
    ...
} else {
    ...
}

7

В связи с ответом Евгения вы можете решить этот конкретный случай, создав объект POJO-оболочки, который содержит в Collection<COrder>качестве своей переменной-члена. Это правильно поможет Джексону поместить фактические Collectionданные в переменную-член POJO и создать JSON, который вы ищете в запросе API.

Пример:

public class ApiRequest {

   @JsonProperty("collection")
   private Collection<COrder> collection;

   // getters
}

Затем установите тип параметра COrderRestService.postOrder()как вашу новую ApiRequestоболочку POJO вместо Collection<COrder>.


2

В эти дни я столкнулся с той же проблемой, и, возможно, некоторые подробности могут быть полезны кому-то другому.

Я искал некоторые рекомендации по безопасности для REST API и преодолел очень интригующую проблему с массивами json. Проверьте ссылку для получения подробной информации, но в основном вы должны заключить их в объект, как мы уже видели в этом вопросе публикации.

Итак, вместо:

  [
    {
      "name": "order1"
    },
    {
      "name": "order2"
    }
  ]

Желательно, чтобы мы всегда делали:

  {
    "data": [
      {
        "name": "order1"
      },
      {
        "name": "order2"
      }
    ]
  }

Это довольно просто, когда вы выполняете GET , но может вызвать некоторые проблемы, если вместо этого вы попытаетесь выполнить POST / PUT тот же самый json.

В моем случае у меня было более одного GET, который был списком, и более одного POST / PUT , которые получали бы один и тот же json.

В итоге я использовал очень простой объект Wrapper для списка :

public class Wrapper<T> {
  private List<T> data;

  public Wrapper() {}

  public Wrapper(List<T> data) {
    this.data = data;
  }
  public List<T> getData() {
    return data;
  }
  public void setData(List<T> data) {
    this.data = data;
  }
}

Сериализация моих списков была произведена с помощью @ControllerAdvice :

@ControllerAdvice
public class JSONResponseWrapper implements ResponseBodyAdvice<Object> {

  @Override
  public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
    return true;
  }

  @Override
  @SuppressWarnings("unchecked")
  public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
    if (body instanceof List) {
      return new Wrapper<>((List<Object>) body);
    }
    else if (body instanceof Map) {
      return Collections.singletonMap("data", body);
    }  
    return body;
  }
}

Итак, все списки и карты были обернуты поверх объекта данных, как показано ниже:

  {
    "data": [
      {...}
    ]
  }

Десериализация по-прежнему была по умолчанию, просто использовался объект de Wrapper :

@PostMapping("/resource")
public ResponseEntity<Void> setResources(@RequestBody Wrapper<ResourceDTO> wrappedResources) {
  List<ResourceDTO> resources = wrappedResources.getData();
  // your code here
  return ResponseEntity
           .ok()
           .build();
}

Вот и все! Надеюсь, это кому-то поможет.

Примечание: протестировано с помощью SpringBoot 1.5.5.RELEASE .


1
класс-оболочка настоящий
Омид Ростами

0

У меня была эта проблема с REST API, созданным с использованием Spring framework. Добавление аннотации @ResponseBody (чтобы сделать ответ JSON) разрешило это.


0

Обычно мы сталкиваемся с этой проблемой, когда возникает проблема сопоставления узла JSON с узлом Java-объекта. Я столкнулся с той же проблемой, потому что в чванстве узел был определен как массив типа, а объект JSON имел только один элемент, поэтому система испытывала трудности с отображением одного списка элементов в массив.

В Swagger элемент был определен как

Test:
 "type": "array",
 "minItems": 1,
 "items": {
   "$ref": "#/definitions/TestNew"
  }

Хотя должно быть

Test:
    "$ref": "#/definitions/TestNew"

И TestNewдолжен быть типа array


0
Dto response = softConvertValue(jsonData, Dto.class);


     public static <T> T softConvertValue(Object fromValue, Class<T> toValueType) 
        {
            ObjectMapper objMapper = new ObjectMapper();
            return objMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
                    .convertValue(fromValue, toValueType);
        }

0

Та же проблема:

com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.util.UUID` out of START_OBJECT token

Причиной этого было следующее:

ResponseEntity<UUID> response = restTemplate.postForEntity("/example/", null, UUID.class);

В моем тесте я намеренно установил запрос как null (без POST содержимого). Как упоминалось ранее, причина для OP была та же, потому что запрос не содержал действительного JSON, поэтому он не мог быть автоматически идентифицирован как запрос application / json, что было ограничением на сервере ( consumes = "application/json"). Действительный запрос JSON будет. Что исправлено, так это явное заполнение объекта нулевым телом и заголовками json.

HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity request = new HttpEntity<>(null, headers);
ResponseEntity<UUID> response = restTemplate.postForEntity("/example/", request, UUID.class);

0

В моем случае ошибка отображалась, потому что, когда я читал свой файл JSON с использованием библиотеки Джексона, мой файл JSON содержал только 1 объект. Следовательно, он начинается с "{" и заканчивается "}". Но, читая его и сохраняя в переменной, я сохранял его в объекте Array (как и в моем случае, могло быть более 1 объекта).

Следовательно, я добавил «[» в начале и «]» в конец моего файла JSON, чтобы преобразовать его в массив объектов, и он работал отлично без каких-либо ошибок.


0

Как упоминалось выше, проблему решило бы следующее: mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);

Однако в моем случае провайдер выполнил сериализацию [0..1] или [0 .. *] скорее как ошибку, и я не мог принудительно исправить. С другой стороны, он не хотел влиять на мой строгий картограф во всех других случаях, которые требуют строгой проверки.

Итак, я сделал ГЛАВНЫЙ ХАК Jackson (который не следует копировать вообще ;-)), особенно потому, что у моего SingleOrListElement было только несколько свойств, которые нужно исправить:

@JsonProperty(value = "SingleOrListElement", access = JsonProperty.Access.WRITE_ONLY)
private Object singleOrListElement; 

public List<SingleOrListElement> patch(Object singleOrListElement) {
  if (singleOrListElement instanceof List) {
    return (ArrayList<SingleOrListElement>) singleOrListElement;
  } else {
    LinkedHashMap map = (LinkedHashMap) singleOrListElement;
    return Collections.singletonList(SingletonList.builder()
                            .property1((String) map.get("p1"))
                            .property2((Integer) map.get("p2"))
                            .build());
  }

-1

@JsonFormat (with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY) частный список заказов <COrder>;


Пожалуйста, предоставьте полный ответ с некоторым кодом внутри кодовых блоков и некоторым текстом, оценивающим правильность вашего ответа
Иоаннис Баракос,
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.