无法从START_OBJECT令牌中反序列化java.util.ArrayList的实例


129

我正在尝试发布List自定义对象。我在请求正文中的JSON是这样的:

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

处理请求的服务器端代码:

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

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

但是抛出一个异常:

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)

Answers:


156

问题是JSON-默认情况下不能反序列化为,Collection因为它实际上不是JSON数组-看起来像这样:

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

由于您没有控制反序列化的确切过程(RestEasy确实如此)- 第一个选择是简单地将JSON作为a注入String,然后控制反序列化过程:

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

您不必自己做,便会失去一些便利,但是您很容易解决问题。

另一个选择 -如果您不能更改JSON-将构造一个包装器以适合JSON输入的结构-并使用而不是Collection<COrder>

希望这可以帮助。


1
太好了,我整理了集合,因为Resteasy文档中有一个示例,但是它是XML的。
isah 2013年

2
@isah很好,请您在这里分享该resteasy链接
nanospeck

想一想。我很确定自己有正确的代码,并调试了几个小时,并在此过程中更改了代码。原来我只是缺少方括号来表明我的帖子是array.Arrg。好吧,我想这是一个新手的诅咒,哈哈。对于JSON和Spring Data的新手,请不要追随我的脚步。:(
iamjoshua

该代码对我不起作用,控制器中的预期代码应该是什么?
prem30488


8

这将起作用:

当您尝试使用JsonArray而不是JsonNode读取具有单个元素的列表时,可能会发生此问题,反之亦然。

由于您无法确定返回的列表是包含单个元素(所以json看起来像这样{...}还是多个元素(而json看起来是这样的[{...},{... }] -您必须在运行时检查元素的类型。

它看起来应该像这样:

(注意:在此代码示例中,我使用的是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

与Eugen的答案有关,您可以通过创建一个包装POJO对象(其中包含一个Collection<COrder>作为其成员变量)来解决这种特殊情况。这将正确指导Jackson将实际Collection数据放入POJO的成员变量中,并生成您在API请求中寻找的JSON。

例:

public class ApiRequest {

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

   // getters
}

然后将的参数类型设置为COrderRestService.postOrder()ApiRequest包装POJO而不是Collection<COrder>


2

这些天,我遇到了同样的问题,也许更多细节可能对其他人有所帮助。

我一直在寻找REST API的一些安全准则,并且遇到了一个与json数组非常有趣的问题。检查链接以获取详细信息,但是基本上,您应该将它们包装在一个对象中,如我们在本帖子问题中已经看到的那样。

因此,代替:

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

建议我们始终这样做:

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

当您执行GET时,这很简单,但是如果尝试发布/放置相同的json ,可能会给您带来麻烦。

就我而言,我有多个作为列表的GET和多个将接收相同json的POST / PUT

所以我最终要做的是为List使用一个非常简单的Wrapper对象:

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

我的列表的序列化是通过@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;
  }
}

因此,所有列表和地图都包裹在一个数据对象上,如下所示:

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

反序列化仍然是默认设置,仅使用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();
}

就是这样!希望它可以帮助某人。

注意:使用SpringBoot 1.5.5.RELEASE测试。


1
包装课是真正的
Omid Rostami

0

我在使用Spring框架创建的REST API上遇到了这个问题。添加@ResponseBody批注(以使响应JSON)解决了它。


0

通常,当映射JSON节点与Java对象的映射节点时遇到问题。我遇到了同样的问题,因为在大张旗鼓中,节点定义为Type数组,并且JSON对象仅包含一个元素,因此系统难以将一个元素列表映射到数组。

在Swagger中,元素定义为

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

虽然应该

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

并且TestNew应该是数组类型


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

相同的问题:

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

造成这种情况的原因如下:

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

在我的测试中,我故意将请求设置为null(无内容POST)。如前所述,OP的原因是相同的,因为该请求不包含有效的JSON,因此无法将其自动识别为应用程序/ json请求,这是服务器(consumes = "application/json")上的限制。有效的JSON请求将会是。解决该问题的方法是显式填充具有null正文和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

在我的情况下,显示错误是因为当我使用Jackson库读取JSON文件时,我的JSON文件仅包含1个对象。因此,它以“ {”开头,以“}”结尾。但是在读取并将其存储在变量中时,我将其存储在Array对象中(就我而言,可能有多个对象)。

因此,我在JSON文件的开头添加了[[],在其末尾添加了[]“,以将其转换为对象数组,并且工作得很好,没有任何错误。


0

如上所述,以下将解决问题: mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);

但是,在我的情况下,提供程序执行了此[0..1]或[0 .. *]序列化,而不是将其作为错误,因此我无法强制执行修复。另一方面,它不想影响需要严格验证的所有其他情况的严格映射器。

所以我做了一个Jackson NASTY HACK(一般不应该复制;-)),特别是因为我的SingleOrListElement只有很少的属性需要修补:

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.