Подтвердить что ты не робот

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

Я пытаюсь POST a 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();
    }
}

Entity 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: [email protected]; 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)
4b9b3361

Ответ 1

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

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

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

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

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

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

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

Ответ 2

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

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

Ответ 3

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

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

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

Он должен выглядеть так:

(Примечание: в этом примере кода я использую 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 {
    ...
}

Ответ 4

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

Пример:

public class ApiRequest {

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

   // getters
}

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

Ответ 5

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

Ответ 6

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

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

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

Пока должно быть

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

И TestNew должен быть типа массив

Ответ 7

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

Ответ 8

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

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

В моем тесте я намеренно установил запрос как нулевой (без содержимого POST). Как упоминалось ранее, причина для OP была той же, потому что запрос не содержал действительный JSON, поэтому он не мог быть автоматически идентифицирован как запрос application/json, который был ограничением на сервере (consumes = "application/json"). Действительный запрос JSON будет. Что исправило это, так это явное заполнение сущности пустыми заголовками body и 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);