无法从 START _ OBJECT 标记反序列化 java.util. ArrayList 的实例

我试图发布一个自定义对象的 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)
558541 次浏览

问题在于 JSON ——默认情况下,它不能被反序列化为 Collection,因为它实际上不是 JSON 数组——它看起来应该是这样的:

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

因为你没有控制反序列化的确切过程(RestEasy 做到了)-第一选择只是简单地将 JSON 作为 String注入,然后控制反序列化过程:

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

你可能会失去一些不必自己动手的便利,但是你可以很容易地解决问题。

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

希望这个能帮上忙。

这将奏效:

当您试图读取一个单个元素为 JsonArray而不是 JsonNode的列表时,可能会出现问题,反之亦然。

因为您不能确定返回的列表是否包含单个元素 (所以 json 看起来像这样 < strong > { ... } )或多个元素 (json 看起来像这样 < strong > [{ ... } ,{ ... }] )- 您必须在运行时检查元素的类型。

它应该是这样的:

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

您可以更新 ObjectMapper 对象,而不是 JSON 文档,如下所示:

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

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

通常,当 JSON 节点与 Java 对象的节点之间存在映射问题时,我们会面临这个问题。我遇到了同样的问题,因为在这个虚张声势中,节点被定义为 Type array,而 JSON 对象只有一个元素,因此系统很难将一个元素列表映射到一个数组。

在斯威格里,元素被定义为

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

应该是的

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

TestNew应该是数组类型的

与 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>

同样的问题:

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 body 和 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);
@JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
private List< COrder > orders;

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

因此,我在 JSON 文件的末尾添加了“[”in the start and“]”,将其转换为对象数组,它工作得非常好,没有任何错误。

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

我正在寻找一些针对 REST API 的安全指南,并使用 json 数组跨越了 非常有趣的问题。检查链接获得详细信息,但基本上,你应该把它们包装在一个对象中,就像我们在这个帖子问题中看到的那样。

所以,不是:

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

我们总是这样做是明智的:

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

当你做 走开的时候,这是相当直接的,但是如果你试图用同样的 json 来做 POST/PUT,可能会给你带来一些麻烦。

在我的情况下,我有一个以上的 走开是一个 名单和一个以上的 POST/PUT将接收非常相同的 json。

所以我最后做的是使用一个非常简单的 包装纸对象作为 名单:

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

我的清单是用 @ Controller 建议连载的:

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

因此,所有包装在 资料对象上的 List 和 Maps 如下所示:

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

反序列化仍然是默认的,只是使用 de 包装纸对象:

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

就是这样,希望对谁有帮助。

注意: 使用 SpringBoot1.5.5发射进行了测试。

如上所述,以下方法可以解决这个问题: mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);

但是在我的例子中,提供程序这样做[0。. 1]或[0]。.* ]序列化而不是 bug,我不能强制修复。另一方面,它不想影响我对所有其他需要严格验证的情况的严格映射。

所以我做了一个 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());
}

在与这个东西斗争了很长时间之后,这里有一个超级简单的解决方案。

我的控制器在找

@RequestBody List<String> ids

我有请求身体作为

{
"ids": [
"1234",
"5678"
]
}

解决办法就是把身体简单地

["1234", "5678"]

对,就这么简单。

我正在处理同样的问题,我想同时存储多个记录。

经过大量的研究,我找到了一个简单的解决办法,立刻奏效了

在解决问题代码之前:

控制器方法

   @PostMapping("/addList")
public ResponseEntity<List<Alarm>> saveAlarmList(@RequestBody List<AlarmDTO> dtos) {
return ResponseEntity.ok(alarmService.saveAlarmList(dtos));
}

服务方式

 public List<Alarm> saveAlarmList(List<AlarmDTO> dtos) {


List<Alarm> alarmList = new ArrayList<>();
for (AlarmDTO dto: dtos) {
Alarm newAlarm = AlarmDtoMapper.map(dto);
alarmList.add(newAlarm);
}


return alarmRepository.saveAll(alarmList);
}

实体类

   public class Alarm{


@Id
@Basic(optional = false)
@NotNull
@Column(name = "alarm_id")
private Long alarmId;


@Basic(optional = false)
@NotNull
@Size(min = 1, max = 100)
@Column(name = "alarm_name")
private String alarmName;

}

邮递员对象邮递员对象

   [
{
"alarmId":"55",
"alarmName":"fgdffg",
},
{
"alarmId":"77788",
"alarmName":"hjjjjfk",
}
]

和另一种格式

      {
"list":   [
{
"alarmId":"55",
"alarmName":"fgdffg",
},
{
"alarmId":"77788",
"alarmName":"hjjjjfk",
}
]
}

在这两种方法中,它都给出了序列化/反序列化的错误。

然后我只是 在 Entity 类中实现了 Serializer 接口如下。

实体类

  public class Alarm implements Serializable
{
@Id
@Basic(optional = false)
@NotNull
@Column(name = "alarm_id")
private Long alarmId;


@Basic(optional = false)
@NotNull
@Size(min = 1, max = 100)
@Column(name = "alarm_name")
private String alarmName;
}

在这个修改之后,来自 postman 的第一个请求对象类型工作了。