No se puede deserializar la instancia de java.util.ArrayList fuera del token START_OBJECT

129

Estoy tratando de publicar un Listobjeto personalizado. Mi JSON en cuerpo de solicitud es este:

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

Código del lado del servidor que maneja la solicitud:

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

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

Pero se lanza una excepción:

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

Respuestas:

156

El problema es el JSON, que por defecto no puede ser deserializado en un Collectionporque no es en realidad una matriz JSON, que se vería así:

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

Como no está controlando el proceso exacto de deserialización (RestEasy sí), una primera opción sería simplemente inyectar el JSON como a Stringy luego tomar el control del proceso de deserialización:

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

Perdería un poco la comodidad de no tener que hacerlo usted mismo, pero resolvería el problema fácilmente.

Otra opción , si no puede cambiar el JSON, sería construir un contenedor que se ajuste a la estructura de su entrada JSON, y usarlo en lugar de Collection<COrder>.

Espero que esto ayude.

Eugen
fuente
1
Genial, terminé en la colección porque había un ejemplo en Resteasy docs, pero fue con XML.
isah
2
@isah agradable, se puede compartir ese enlace resteasy aquí, por favor
nanospeck
ahora que lo pienso. Estoy bastante seguro de que tengo el código correcto y lo depuro durante horas, y cambio mis códigos en el camino. Resulta que solo faltaban los corchetes para indicar que mi publicación es una matriz. Bueno, supongo que esta es una maldición de novatos, jajaja. Para cualquier persona nueva en JSON y Spring Data, no siga mis pasos. :(
iamjoshua
El código no me funciona, en el controlador, ¿cuál debería ser el código esperado?
prem30488
62

En lugar del documento JSON, puede actualizar el objeto ObjectMapper como se muestra a continuación:

ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
Salah Atwa
fuente
1
Gracias, la respuesta me ayudó
Jesús Sánchez
¡Fantástico! Salvaste un día.
Herve Mutombo
gracias, por favor refiérase a nuestro sitio mboot.herokuapp.com , publicamos artículo relacionado java - spring-boot
Salah Atwa
8

Esto funcionará:

El problema puede ocurrir cuando intentas leer una lista con un solo elemento como JsonArray en lugar de JsonNode o viceversa.

Como no puede saber con certeza si la lista devuelta contiene un solo elemento (por lo que el json se ve así {...} ) o varios elementos (y el json se ve así [{...}, {... }] ) - deberás comprobar en tiempo de ejecución el tipo de elemento.

Debe tener un aspecto como este:

(Nota: en este ejemplo de código estoy 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 {
    ...
}
Bar Naor
fuente
7

En relación con la respuesta de Eugen, puede resolver este caso particular creando un objeto POJO de contenedor que contenga a Collection<COrder>como su variable miembro. Esto guiará adecuadamente a Jackson para colocar los Collectiondatos reales dentro de la variable miembro de POJO y producir el JSON que está buscando en la solicitud de API.

Ejemplo:

public class ApiRequest {

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

   // getters
}

Luego establezca el tipo de parámetro de COrderRestService.postOrder()para que sea su nuevo ApiRequestcontenedor POJO en lugar de Collection<COrder>.

Adil B
fuente
2

Me encontré con este mismo problema en estos días y tal vez algún detalle más podría ser útil para otra persona.

Estaba buscando algunas pautas de seguridad para las API REST y crucé un problema muy intrigante con las matrices json. Verifique el enlace para obtener detalles, pero básicamente, debe envolverlos dentro de un Objeto como ya vimos en esta pregunta de la publicación.

Entonces, en lugar de:

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

Es recomendable que siempre hagamos:

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

Esto es bastante sencillo cuando haces un GET , pero podría darte algunos problemas si, en cambio, intentas PUBLICAR / PONER el mismo json.

En mi caso, tenía más de un GET que era una Lista y más de un POST / PUT que recibiría el mismo json.

Entonces, lo que terminé haciendo fue usar un objeto Wrapper muy simple para una Lista :

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 serialización de mis listas se realizó 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;
  }
}

Entonces, todas las Listas y Mapas se envolvieron sobre un objeto de datos como se muestra a continuación:

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

La deserialización seguía siendo predeterminada, solo usando el Objeto Wrapper :

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

¡Eso fue todo! Espero que ayude a alguien.

Nota: probado con SpringBoot 1.5.5.RELEASE .

Reginaldo Santos
fuente
1
la clase de envoltura es genuina
Omid Rostami
0

Tuve este problema en una API REST que se creó con Spring Framework. Agregar una anotación @ResponseBody (para hacer la respuesta JSON) lo resolvió.

Hacer voluntad
fuente
0

Normalmente nos enfrentamos a este problema cuando hay un problema al mapear el nodo JSON con el del objeto Java. Me enfrenté al mismo problema porque en el swagger el nodo se definió como tipo de matriz y el objeto JSON tenía solo un elemento, por lo tanto, el sistema tenía dificultades para asignar una lista de elementos a una matriz.

En Swagger, el elemento se definió como

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

Si bien debería ser

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

Y TestNewdebe ser de tipo array

Ambuj Sinha
fuente
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);
        }
Dipen Chawla
fuente
0

Mismo problema:

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

Lo que lo causó fue lo siguiente:

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

En mi prueba, configuré a propósito la solicitud como nula (sin contenido POST). Como se mencionó anteriormente, la causa del OP fue la misma porque la solicitud no contenía un JSON válido, por lo que no se pudo identificar automáticamente como una solicitud de aplicación / json que era la limitación en el servidor ( consumes = "application/json"). Sería una solicitud JSON válida. Lo que solucionó fue poblar una entidad con cuerpo nulo y encabezados json explícitamente.

HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity request = new HttpEntity<>(null, headers);
ResponseEntity<UUID> response = restTemplate.postForEntity("/example/", request, UUID.class);
Tanel
fuente
0

En mi caso, el error se mostraba porque cuando estaba leyendo mi archivo JSON usando la biblioteca Jackson, mi archivo JSON contenía solo 1 objeto. Por lo tanto, comenzó con "{" y terminó con "}". Pero mientras lo leía y lo almacenaba en una variable, lo estaba almacenando en un objeto Array (como en mi caso, podría haber más de 1 objeto).

Por lo tanto, agregué "[" al principio y "]" al final de mi archivo JSON para convertirlo en una matriz de objetos y funcionó perfectamente bien sin ningún error.

Akshay Chopra
fuente
0

Como se mencionó anteriormente, lo siguiente resolvería el problema: mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);

Sin embargo, en mi caso, el proveedor hizo esta serialización [0..1] o [0 .. *] en lugar de un error y no pude forzar la reparación. Por otro lado, no quería afectar mi mapeador estricto para todos los demás casos que deben validarse estrictamente.

Así que hice un Jackson NASTY HACK (que no debería copiarse en general ;-)), especialmente porque mi SingleOrListElement tenía solo pocas propiedades para parchear:

@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());
  }
phirzel
fuente
-1

@JsonFormat (con = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY) Lista privada <COrder> pedidos;

Suhas Saheer
fuente
Proporcione una respuesta completa con algún código dentro de los bloques de código y un texto que evalúe la exactitud de su respuesta
Ioannis Barakos