Неможливо десериалізувати примірник 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 - це за замовчуванням не може бути деріаріалізовано в a, 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 docs, але це було з XML.
isah

2
@isah приємно, ви можете поділитися цим прямим посиланням тут будь ласка
nanospeck

приходьте подумати про це. Я майже впевнений, що я маю правильний код і налагоджую це протягом годин, а також змінюю свої коди по ходу. Виявляється, я просто бракував квадратних дужок, щоб вказати, що моя посада - це масив. Арг. Ну я здогадуюсь це прокляття для новачків, 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()щоб ваш новий ApiRequestPOJO обгортки Collection<COrder>.


2

Я зіткнувся з цією самою проблемою і сьогодні, і, можливо, ще якась деталь може бути корисною для когось іншого.

Я шукав деякі вказівки щодо безпеки API REST та перетнув дуже інтригуючу проблему з масивами 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
клас обгортки справжній
Omid Rostami

0

У мене ця проблема була в API REST, створеному за допомогою Spring Framework. Додавання анотації @ResponseBody (для надання відповіді JSON) вирішило її.


0

Зазвичай ми стикаємося з цією проблемою, коли виникає проблема зі зіставленням вузла JSON з об'єктом Java. Я зіткнувся з тією ж проблемою, оскільки в шахрайстві вузол був визначений як тип масиву, а об'єкт JSON мав лише один елемент, отже, у системи виникли труднощі з відображенням одного списку елементів до масиву.

У Swagger елемент був визначений як

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

Поки має бути

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

І TestNewмає мати тип масиву


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). Як було сказано раніше, причина для ОП була такою ж, оскільки запит не містив дійсного JSON, тому його не можна було автоматично ідентифікувати як запит / 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 NASTY HACK (який взагалі не слід копіювати ;-)), тим більше, що мій 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 (з = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY) приватний список <COrder> замовлення;


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