我正在尝试发布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)
答案 0 :(得分:116)
问题是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>
。
希望这会有所帮助。
答案 1 :(得分:16)
您可以更新ObjectMapper对象而不是JSON文档,如下所示:
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
答案 2 :(得分:7)
这样可行:
当您尝试将包含单个元素的列表读取为 JsonArray 而不是 JsonNode 时,可能会出现此问题,反之亦然。
由于您无法确定返回的列表是否包含单个元素(因此json看起来像 {...} )或多个元素< em>(和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 {
...
}
答案 3 :(得分:3)
这几天我遇到了同样的问题,也许更多细节可能对其他人有帮助。
我正在寻找REST API的一些安全准则,并用json数组越过了very intriguing issue。检查链接以获取详细信息,但是基本上,您应该将它们包装在一个对象中,如我们在本帖子问题中已经看到的那样。
所以,而不是:
[
{
"name": "order1"
},
{
"name": "order2"
}
]
建议我们始终这样做:
{
"data": [
{
"name": "order1"
},
{
"name": "order2"
}
]
}
这在执行 GET 时非常简单,但是如果尝试 POST / PUT 相同的json,可能会给您带来一些麻烦。
在我的情况下,我有多个 GET 是一个 List ,还有多个 POST / PUT 会收到相同的json。
所以我最终要做的是对 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 对象上,如下所示:
{
"data": [
{...}
]
}
反序列化仍然是默认设置,仅使用 Wrapper 对象:
@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 测试。
答案 4 :(得分:3)
如上所述,可以解决以下问题: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());
}
答案 5 :(得分:1)
在这件事上挣扎了太久之后,这里有一个超级简单的解决方案。
我的控制器正在寻找
@RequestBody List<String> ids
我的请求正文为
{
"ids": [
"1234",
"5678"
]
}
解决方案是将 body 简单地更改为
["1234", "5678"]
是的。就这么简单。
答案 6 :(得分:0)
我在使用Spring框架创建的REST API上遇到了这个问题。添加@ResponseBody批注(以使响应JSON)解决了它。
答案 7 :(得分:0)
通常,当映射JSON节点与Java对象的映射节点时出现问题。我遇到了同样的问题,因为在大张旗鼓中,节点定义为Type数组,并且JSON对象仅包含一个元素,因此系统难以将一个元素列表映射到数组。
在Swagger中,元素定义为
Test:
"type": "array",
"minItems": 1,
"items": {
"$ref": "#/definitions/TestNew"
}
应该是
Test:
"$ref": "#/definitions/TestNew"
并且TestNew
应该是数组类型
答案 8 :(得分:0)
与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>
。
答案 9 :(得分: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);
}
答案 10 :(得分: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);
答案 11 :(得分:0)
在我的情况下,显示错误是因为当我使用Jackson图书馆读取JSON文件时,我的JSON文件仅包含1个对象。因此,它以“ {”开头,以“}”结尾。但是在读取并将其存储在变量中时,我将其存储在Array对象中(就我而言,可能有多个对象)。
因此,我在JSON文件的开头添加了[[],并在其末尾添加了[]”,以将其转换为对象数组,并且工作得很好,没有任何错误。
答案 12 :(得分:-1)
@JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
私人List