Impossibile deserializzare l'istanza di java.util.ArrayList dal token START_OBJECT


129

Sto provando a POSTARE un Listoggetto personalizzato. Il mio JSON nel corpo della richiesta è questo:

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

Codice lato server che gestisce la richiesta:

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

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

Ma viene generata un'eccezione:

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)

Risposte:


156

Il problema è che JSON - questo non può, per impostazione predefinita, essere deserializzato in un Collectionperché non è in realtà un array JSON - che assomiglierebbe a questo:

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

Dal momento che non stai controllando il processo esatto di deserializzazione (RestEasy fa) - una prima opzione sarebbe quella di iniettare semplicemente JSON come un Stringe quindi prendere il controllo del processo di deserializzazione:

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

Perderesti un po 'della comodità di non doverlo fare da solo, ma risolveresti facilmente il problema.

Un'altra opzione , se non è possibile modificare JSON, sarebbe quella di costruire un wrapper per adattarsi alla struttura dell'input JSON e usarlo invece di Collection<COrder>.

Spero che questo ti aiuti.


1
Fantastico, ho racchiuso la raccolta perché c'era un esempio nei documenti Resteasy, ma era con XML.
isah,

2
@isah bello, puoi condividere quel link tranquillo qui per favore
nanospeck

vieni a pensarci. Sono abbastanza sicuro di avere il codice giusto e di eseguire il debug per ore e di cambiare i miei codici lungo la strada. Si scopre che mancavano solo le parentesi quadre per indicare che il mio post è un array.Arrg. Beh, immagino che questa sia una maledizione per i neofiti, LOL. A chiunque non conosca JSON e Spring Data, non seguire i miei passi. :(
iamjoshua,

Il codice non funziona per me, nel controller quale dovrebbe essere il codice previsto?
prem30488

62

Invece del documento JSON, puoi aggiornare l'oggetto ObjectMapper come di seguito:

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

1
Grazie, la risposta mi ha aiutato
Jesús Sánchez, il

Fantastico! Hai salvato un giorno.
Herve Mutombo,

grazie, si prega di fare riferimento al nostro sito mboot.herokuapp.com , pubblichiamo l'articolo relativo a java - spring-boot
Salah Atwa

8

Questo funzionerà:

Il problema può verificarsi quando si tenta di leggere un elenco con un singolo elemento come JsonArray anziché come JsonNode o viceversa.

Dal momento che non puoi sapere con certezza se l'elenco restituito contiene un singolo elemento (quindi il json assomiglia a questo {...} ) o più elementi (e il json assomiglia a questo [{...}, {... }] ) - dovrai verificare in runtime il tipo di elemento.

Dovrebbe sembrare come questo:

(Nota: in questo esempio di codice sto usando 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

In relazione alla risposta di Eugen, puoi risolvere questo caso particolare creando un oggetto POJO wrapper che contiene un Collection<COrder>come variabile membro. Questo guiderà correttamente Jackson nel posizionare i Collectiondati effettivi all'interno della variabile membro del POJO e produrre il JSON che stai cercando nella richiesta API.

Esempio:

public class ApiRequest {

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

   // getters
}

Quindi impostare il tipo di parametro COrderRestService.postOrder()come nuovo ApiRequestPOJO del wrapper anziché Collection<COrder>.


2

Ho riscontrato questo stesso problema in questi giorni e forse qualche dettaglio in più potrebbe essere utile a qualcun altro.

Stavo cercando alcune linee guida di sicurezza per le API REST e ho riscontrato un problema molto interessante con le matrici json. Controlla il link per i dettagli, ma in sostanza, dovresti avvolgerli in un oggetto come abbiamo già visto in questa domanda post.

Quindi, invece di:

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

È consigliabile che facciamo sempre:

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

Questo è piuttosto semplice quando fai un GET , ma potrebbe darti qualche problema se, invece, stai provando a POST / PUT lo stesso json.

Nel mio caso, avevo più di un GET che era un elenco e più di un POST / PUT che avrebbero ricevuto lo stesso json.

Quindi quello che finisco per fare è usare un oggetto Wrapper molto semplice per un elenco :

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

La serializzazione delle mie liste è stata effettuata con un @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;
  }
}

Quindi, tutti gli elenchi e le mappe sono stati racchiusi in un oggetto dati come di seguito:

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

La deserializzazione era ancora predefinita, usando solo 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();
}

Questo è stato! Spero che aiuti qualcuno.

Nota: testato con SpringBoot 1.5.5.RELEASE .


1
la classe wrapper è autentica
Omid Rostami il

0

Ho riscontrato questo problema su un'API REST creata utilizzando il framework Spring. Aggiunta di un'annotazione @ResponseBody (per rendere la risposta JSON) risolta.


0

Normalmente affrontiamo questo problema quando c'è un problema nel mappare il nodo JSON con quello dell'oggetto Java. Ho affrontato lo stesso problema perché nello swagger il nodo era definito come di tipo array e l'oggetto JSON aveva un solo elemento, quindi il sistema aveva difficoltà a mappare un elenco di elementi su un array.

In Swagger l'elemento è stato definito come

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

Mentre dovrebbe essere

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

E TestNewdovrebbe essere di tipo 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

Stesso problema:

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

Ciò che ha causato è stato il seguente:

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

Nel mio test ho volutamente impostato la richiesta su null (nessun contenuto POST). Come accennato in precedenza, la causa dell'OP era la stessa perché la richiesta non conteneva un JSON valido, quindi non poteva essere identificato automaticamente come richiesta application / json che era la limitazione sul server ( consumes = "application/json"). Una richiesta JSON valida sarebbe. Ciò che risolveva era il popolamento esplicito di un'entità con intestazioni null body e 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

Nel mio caso, l'errore veniva mostrato perché quando stavo leggendo il mio file JSON utilizzando la libreria Jackson, il mio file JSON conteneva solo 1 oggetto. Quindi è iniziato con "{" e si è concluso con "}". Ma mentre lo leggevo e lo memorizzavo in una variabile, lo stavo memorizzando in un oggetto Array (Come nel mio caso, potrebbe esserci più di 1 oggetto).

Quindi, ho aggiunto "[" all'inizio e "]" alla fine del mio file JSON per convertirlo in un array di oggetti e ha funzionato perfettamente senza errori.


0

Come accennato in precedenza, il seguente risolverebbe il problema: mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);

Tuttavia, nel mio caso, il provider ha eseguito questa serializzazione [0..1] o [0 .. *] piuttosto che un bug e non ho potuto applicare la correzione. D'altra parte, non voleva influire sul mio rigoroso mapper per tutti gli altri casi che devono essere validati rigorosamente.

Quindi ho fatto un Jackson NASTY HACK (che non dovrebbe essere copiato in generale ;-)), soprattutto perché il mio SingleOrListElement aveva solo poche proprietà da correggere:

@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 (con = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY) ordini Elenco privato <COrder>;


Fornisci una risposta completa con del codice all'interno di blocchi di codice e del testo per valutare la correttezza della tua risposta
Ioannis Barakos,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.