Nie można deserializować wystąpienia java.util.ArrayList z START_OBJECT tokenu


129

Próbuję OPUBLIKOWAĆ Listobiekty niestandardowe. Mój kod JSON w treści żądania to:

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

Kod po stronie serwera obsługujący żądanie:

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

Podmiot 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() + "]";
    }
}

Ale wyjątek jest rzucany:

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)

Odpowiedzi:


156

Problemem jest JSON - domyślnie nie można go zdeserializować na a, Collectionponieważ w rzeczywistości nie jest to tablica JSON - to wyglądałoby tak:

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

Ponieważ nie kontrolujesz dokładnego procesu deserializacji (RestEasy tak robi) - pierwszą opcją byłoby po prostu wstrzyknięcie JSON jako a, Stringa następnie przejęcie kontroli nad procesem deserializacji:

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

Trochę straciłbyś wygodę, że nie musiałbyś tego robić samodzielnie, ale łatwo rozwiązałbyś problem.

Inną opcją - jeśli nie możesz zmienić JSON - byłoby skonstruowanie opakowania, aby pasowało do struktury danych wejściowych JSON - i użycie tego zamiast Collection<COrder>.

Mam nadzieję że to pomoże.


1
Świetnie, zapakowałem kolekcję, ponieważ w dokumentach Resteasy był przykład, ale był z XML.
isah

2
@isah nice, czy możesz udostępnić tutaj ten link
resteasy

Jeśli pomyśleć o tym. Jestem prawie pewien, że mam odpowiedni kod i debuguję go godzinami i zmieniam kody po drodze. Okazuje się, że brakowało mi nawiasów kwadratowych, aby wskazać, że mój post to array.Arg. Cóż, myślę, że to klątwa dla początkujących, LOL. Każdemu nowemu w JSON i Spring Data nie podążaj moimi śladami. :(
iamjoshua

Kod nie działa u mnie, w kontrolerze jaki powinien być kod oczekiwany?
prem30488

63

Zamiast dokumentu JSON możesz zaktualizować obiekt ObjectMapper jak poniżej:

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

1
Dziękuję, odpowiedź mi pomogła
Jesús Sánchez

Fantastyczny! Uratowałeś dzień.
Herve Mutombo

dzięki, zajrzyj na naszą stronę mboot.herokuapp.com , publikujemy artykuł związany z java - spring-boot
Salah Atwa

8

To zadziała:

Problem może wystąpić, gdy próbujesz odczytać listę z jednym elementem jako JsonArray, a nie JsonNode lub odwrotnie.

Ponieważ nie możesz być pewien, czy zwrócona lista zawiera pojedynczy element (więc json wygląda tak {...} ), czy wiele elementów (a json wygląda tak: [{...}, {... }] ) - będziesz musiał sprawdzić w czasie wykonywania typ elementu.

To powinno wyglądać tak:

(Uwaga: w tym przykładzie kodu używam 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

W związku z odpowiedzią Eugena, możesz rozwiązać ten konkretny przypadek, tworząc obiekt opakowujący POJO, który zawiera Collection<COrder>zmienną składową a. To właściwie poprowadzi Jacksona do umieszczenia rzeczywistegoCollection danych w zmiennej składowej POJO i wygenerowania JSON, którego szukasz w żądaniu API.

Przykład:

public class ApiRequest {

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

   // getters
}

Następnie ustaw typ parametru COrderRestService.postOrder()na nowy ApiRequestPOJO opakowania zamiast Collection<COrder>.


2

Ostatnio napotkałem ten sam problem i może trochę więcej szczegółów może pomóc komuś innemu.

Szukałem wskazówek dotyczących bezpieczeństwa dla interfejsów API REST i napotkałem bardzo intrygujący problem z tablicami json. Sprawdź łącze, aby uzyskać szczegółowe informacje, ale zasadniczo powinieneś zawinąć je w obiekt, jak już widzieliśmy w tym pytaniu.

Więc zamiast:

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

Wskazane jest, abyśmy zawsze:

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

Jest to całkiem proste, gdy wykonujesz GET , ale może sprawić ci trochę problemów, jeśli zamiast tego spróbujesz POST / PUT tego samego pliku json.

W moim przypadku miałem więcej niż jeden GET, który był listą i więcej niż jeden POST / PUT , który otrzymywałby ten sam plik json.

Więc to, co ostatecznie zrobiłem, to użycie bardzo prostego obiektu Wrapper dla listy :

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

Serializacja moich list została wykonana za pomocą @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;
  }
}

Tak więc wszystkie listy i mapy były opakowane w obiekt danych, jak poniżej:

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

Deserializacja była nadal domyślna, po prostu używano de Wrapper Object:

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

To było to! Mam nadzieję, że to komuś pomoże.

Uwaga: przetestowano z SpringBoot 1.5.5.RELEASE .


1
klasa opakowania jest oryginalna
Omid Rostami

0

Miałem ten problem na REST API, które zostało utworzone przy użyciu Spring Framework. Dodanie adnotacji @ResponseBody (w celu utworzenia odpowiedzi JSON) rozwiązało problem.


0

Zwykle mamy do czynienia z tym problemem, gdy występuje problem z mapowaniem węzła JSON na węzeł Java. Napotkałem ten sam problem, ponieważ w swaggerze węzeł został zdefiniowany jako tablica Type, a obiekt JSON miał tylko jeden element, stąd system miał trudności z mapowaniem jednej listy elementów do tablicy.

W Swagger element został zdefiniowany jako

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

Tak powinno być

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

I TestNewpowinien być typu tablica


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

Ten sam problem:

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

Przyczyny tego były następujące:

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

W moim teście celowo ustawiłem żądanie jako null (brak treści POST). Jak wspomniano wcześniej, przyczyna OP była taka sama, ponieważ żądanie nie zawierało prawidłowego kodu JSON, więc nie mogło zostać automatycznie zidentyfikowane jako żądanie aplikacji / json, co było ograniczeniem na serwerze ( consumes = "application/json"). Prawidłowe żądanie JSON byłoby. Naprawiono to, że jawne wypełnianie jednostki z null body i nagłówkami 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

W moim przypadku błąd był wyświetlany, ponieważ kiedy czytałem plik JSON przy użyciu biblioteki Jacksona, mój plik JSON zawierał tylko 1 obiekt. Dlatego zaczęło się od „{” i kończyło na „}”. Ale czytając go i zapisując w zmiennej, przechowywałem go w obiekcie Array (tak jak w moim przypadku może być więcej niż 1 obiekt).

Dlatego dodałem „[” na początku i „]” na końcu mojego pliku JSON, aby przekonwertować go na tablicę obiektów i działało doskonale bez żadnego błędu.


0

Jak wspomniano powyżej, poniższe rozwiązania rozwiązałyby problem: mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);

Jednak w moim przypadku dostawca zrobił to [0..1] lub [0 .. *] serializacji raczej jako błąd i nie mogłem wymusić naprawy. Z drugiej strony nie chciał wpływać na mój ścisły program mapujący dla wszystkich innych przypadków, które wymagają ścisłej weryfikacji.

Zrobiłem więc Jacksona NASTY HACK (którego generalnie nie powinno się kopiować ;-)), zwłaszcza, że ​​mój SingleOrListElement miał tylko kilka właściwości do załatania:

@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 (z = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY) prywatne zamówienia Lista <COrder>;


Proszę podać pełną odpowiedź z kodem w blokach kodu i tekstem oceniającym poprawność odpowiedzi
Ioannis Barakos
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.