web-dev-qa-db-fra.com

Impossible de désérialiser l'instance de Java.util.ArrayList à partir du jeton START_OBJECT

J'essaie de POST une List d'objets personnalisés. Mon JSON dans le corps de la demande est le suivant:

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

Code côté serveur qui gère la demande:

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

Mais une exception est levée:

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)
94
isah

Le problème est le JSON - cela ne peut pas, par défaut, être désérialisé en un Collection car ce n'est pas réellement un tableau JSON - cela ressemblerait à ceci:

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

Puisque vous ne contrôlez pas le processus exact de désérialisation (RestEasy le fait) - ne première option consisterait simplement à injecter le JSON en tant que String, puis à prendre le contrôle du processus de désérialisation:

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

Vous perdriez un peu de la commodité de ne pas avoir à le faire vous-même, mais vous régleriez facilement le problème.

ne autre option - si vous ne pouvez pas changer le JSON - serait de construire un wrapper pour adapter la structure de votre entrée JSON - et l'utiliser au lieu de Collection<COrder>.

J'espère que cela t'aides.

129
Eugen

Au lieu du document JSON, vous pouvez mettre à jour l'objet ObjectMapper comme ci-dessous:

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

Cela fonctionnera:

Le problème peut survenir lorsque vous essayez de lire une liste avec un seul élément sous la forme JsonArray plutôt que JsonNode ou inversement.

Comme vous ne pouvez pas savoir avec certitude si la liste renvoyée contient un seul élément (le json ressemble à ceci {...}) ou plusieurs éléments (et le json ressemble à ceci [{...}, {...}]) - vous devrez vérifier à l'exécution le type de l'élément.

Ça devrait ressembler à ça:

(Remarque: dans cet exemple de code, j'utilise 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
Naor Bar

En lien avec la réponse d'Eugen, vous pouvez résoudre ce cas particulier en créant un objet POJO wrapper contenant un Collection<COrder> en tant que variable membre. Cela guidera correctement Jackson à placer les données Collection réelles dans la variable membre de POJO et à produire le JSON que vous recherchez dans la demande d'API.

Exemple:

public class ApiRequest {

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

   // getters
}

Définissez ensuite le type de paramètre de COrderRestService.postOrder() pour qu'il soit votre nouveau ApiRequest wrapper POJO au lieu de Collection<COrder>.

4
Adil B

Même problème:

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

Quelle en est la cause?

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

Dans mon test, j'ai volontairement défini la demande sur null (pas de contenu POST). Comme mentionné précédemment, la cause de l'OP était la même, car la demande ne contenait pas de code JSON valide. Par conséquent, il n'a pas pu être identifié automatiquement comme une demande application/json, ce qui constituait la limitation du serveur (consumes = "application/json" ) Une demande JSON valide serait. Ce qui a été corrigé, c’était de remplir explicitement une entité avec un corps nul et un en-tête 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
Tanel

Ce problème se posait sur une API REST créée à l'aide de la structure Spring. L'ajout d'une annotation @ResponseBody (pour que la réponse JSON) soit résolue.

0
Do Will
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
Dipen Chawla

Nous sommes normalement confrontés à ce problème lorsqu'un mappage de noeud JSON avec celui de l'objet Java apparaît. J'ai rencontré le même problème car dans le swagger, le nœud était défini comme un tableau de types et que l'objet JSON ne comportait qu'un seul élément. Par conséquent, le système avait des difficultés à mapper une liste d'éléments sur un tableau.

Dans Swagger, l'élément était défini comme

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

Alors qu'il devrait être

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

Et TestNew devrait être de type array

0
Ambuj Sinha