Não é possível desserializar a instância do java.util.ArrayList fora do token START_OBJECT

129

Estou tentando postar um Listde objetos personalizados. Meu JSON no corpo da solicitação é este:

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

Código do lado do servidor que lida com a solicitação:

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

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

Mas uma exceção é lançada:

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
fonte

Respostas:

156

O problema é o JSON - isso não pode, por padrão, ser desserializado para um Collectionporque, na verdade, não é um array JSON - seria assim:

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

Como você não está controlando o processo exato de desserialização (o RestEasy faz) - uma primeira opção seria simplesmente injetar o JSON como um Stringe então assumir o controle do processo de desserialização:

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

Você perderia um pouco da comodidade de não ter que fazer isso sozinho, mas resolveria facilmente o problema.

Outra opção - se você não pode alterar o JSON - seria construir um wrapper para ajustar a estrutura de sua entrada JSON - e usá-lo em vez de Collection<COrder>.

Espero que isto ajude.

Eugen
fonte
1
Ótimo, envolvi-me na coleção porque havia um exemplo nos documentos do Resteasy, mas era com XML.
Isah
2
@isah agradável, você pode compartilhar esse link RESTEasy aqui, por favor
nanospeck
pensando bem. Tenho certeza de que tenho o código certo e depuro-o por horas e altero meus códigos ao longo do caminho. Acontece que eu estava faltando colchetes para indicar que minha postagem é uma matriz. Bem, eu acho que isso é uma maldição para iniciantes, risos. Para quem é novo no JSON e no Spring Data, não siga meus passos. :(
iamjoshua
O código não está funcionando para mim, no controlador, qual deve ser o código esperado?
prem30488 9/06
62

Em vez do documento JSON, você pode atualizar o objeto ObjectMapper como abaixo:

ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
Salah Atwa
fonte
1
Obrigado, a resposta me ajudou
Jesús Sánchez
Fantástico! Você salvou um dia.
Herve Mutombo
obrigado, consulte o nosso site mboot.herokuapp.com , publicamos artigos relacionados a java - spring-boot
Salah Atwa
8

Isso funcionará:

O problema pode ocorrer quando você está tentando ler uma lista com um único elemento como um JsonArray em vez de um JsonNode ou vice-versa.

Como você não pode ter certeza se a lista retornada contém um único elemento (portanto, o json se parece com isso {...} ) ou vários elementos (e o json se parece com isso [{...}, {... }] ) - você terá que verificar em tempo de execução o tipo do elemento.

Deve ficar assim:

(Nota: neste exemplo de código, estou 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 {
    ...
}
Naor Bar
fonte
7

Relacionado à resposta de Eugen, você pode resolver esse caso específico criando um objeto POJO de wrapper que contém a Collection<COrder>como sua variável de membro. Isso orientará adequadamente Jackson a colocar os Collectiondados reais dentro da variável de membro do POJO e produzir o JSON que você está procurando na solicitação da API.

Exemplo:

public class ApiRequest {

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

   // getters
}

Em seguida, defina o tipo de parâmetro COrderRestService.postOrder()como seu novo ApiRequestPOJO do wrapper em vez de Collection<COrder>.

Adil B
fonte
2

Encontrei esse mesmo problema hoje em dia e talvez mais alguns detalhes possam ser úteis para outra pessoa.

Eu estava procurando algumas diretrizes de segurança para APIs REST e cruzei um problema muito intrigante com matrizes json. Verifique o link para obter detalhes, mas, basicamente, você deve envolvê-los em um Objeto, como já vimos nesta pergunta.

Então, em vez de:

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

É aconselhável que sempre façamos:

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

Isso é bastante direto quando você faz um GET , mas pode lhe dar alguns problemas se, em vez disso, você estiver tentando POST / PUT o mesmo json.

No meu caso, eu tinha mais de um GET que era uma lista e mais de um POST / PUT que receberia o mesmo json.

Então, o que acabei fazendo foi usar um objeto Wrapper muito simples para uma 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;
  }
}

A serialização de minhas listas foi feita com um @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;
  }
}

Portanto, todas as listas e mapas foram agrupadas em um objeto de dados como abaixo:

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

A desserialização ainda era o padrão, apenas usando o Objeto Wrapper :

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

Foi isso! Espero que ajude alguém.

Nota: testado com SpringBoot 1.5.5.RELEASE .

Reginaldo Santos
fonte
1
a classe wrapper é genuína
Omid Rostami
0

Eu tive esse problema em uma API REST que foi criada usando a estrutura Spring. A adição de uma anotação @ResponseBody (para tornar a resposta JSON) a resolveu.

Vai fazer
fonte
0

Normalmente, enfrentamos esse problema quando há um problema no mapeamento do nó JSON com o do objeto Java. Eu enfrentei o mesmo problema porque, na arrogância, o nó foi definido como da matriz Type e o objeto JSON estava tendo apenas um elemento; portanto, o sistema estava tendo dificuldade em mapear uma lista de elementos para uma matriz.

Em Swagger, o elemento foi definido como

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

Embora deva ser

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

E TestNewdeve ser do tipo array

Ambuj Sinha
fonte
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
fonte
0

O mesmo problema:

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

O que causou foi o seguinte:

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

No meu teste, defini propositadamente a solicitação como nula (sem conteúdo POST). Como mencionado anteriormente, a causa do OP foi a mesma, porque a solicitação não continha um JSON válido, portanto não pôde ser automaticamente identificada como uma solicitação de aplicativo / json, que era a limitação no servidor ( consumes = "application/json"). Uma solicitação JSON válida seria. O que o corrigiu foi o preenchimento de uma entidade com corpo nulo e cabeçalhos json explicitamente.

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
fonte
0

No meu caso, o erro estava sendo mostrado porque, quando eu estava lendo meu arquivo JSON usando a biblioteca Jackson, meu arquivo JSON continha apenas 1 objeto. Por isso, começou com "{" e terminou com "}". Mas enquanto lia e armazenava em uma variável, eu estava armazenando em um objeto Array (como no meu caso, poderia haver mais de 1 objeto).

Portanto, adicionei "[" no início e "]" no final do meu arquivo JSON para convertê-lo em uma matriz de objetos e funcionou perfeitamente sem nenhum erro.

Akshay Chopra
fonte
0

Como mencionado acima, o seguinte resolveria o problema: mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);

No entanto, no meu caso, o provedor fez essa serialização [0..1] ou [0 .. *] em vez de um bug e eu não pude aplicar a correção. Por outro lado, não queria impactar meu mapeador estrito para todos os outros casos que precisam ser validados estritamente.

Então eu fiz um Jackson NASTY HACK (que não deve ser copiado em geral ;-)), especialmente porque meu SingleOrListElement tinha apenas poucas propriedades para corrigir:

@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
fonte
-1

@JsonFormat (com = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY) private List <COrder> pedidos;

Suhas Saheer
fonte
Por favor, forneça uma resposta completa com alguns blocos de código de código dentro e algum texto avaliar a justeza da sua resposta
Ioannis Barakos