【问题标题】:Can not deserialize instance of java.util.ArrayList out of START_OBJECT token无法从 START_OBJECT 令牌中反序列化 java.util.ArrayList 的实例
【发布时间】:2014-01-17 05:44:33
【问题描述】:

我正在尝试发布 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)

【问题讨论】:

    标签: java spring jackson jax-rs resteasy


    【解决方案1】:

    问题在于 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&lt;COrder&gt;

    希望这会有所帮助。

    【讨论】:

    • 太好了,我在集合中进行了包装,因为 Resteasy 文档中有一个示例,但它使用的是 XML。
    • @isah 很好,你能在这里分享那个简单的链接吗
    • 想想吧。我很确定我有正确的代码并调试了几个小时,并在此过程中更改了我的代码。原来我只是缺少方括号来表明我的帖子是一个array.Arrg。好吧,我想这是一个新手诅咒,哈哈。对于 JSON 和 Spring Data 的新手,请不要跟随我的脚步。 :(
    • 代码对我不起作用,在控制器中应该是什么代码?
    • 完美运行!
    【解决方案2】:

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

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

    【讨论】:

    • 这帮助我“忽略”了错误的括号......并在组合 mit JsonNode 并在杰克逊解析让我再次运行之前将其转换为 String :-) ... JsonNode oJsonNode = oJacksonObjectMapper.readTree(sJsonString); 和 @ 987654323@和mapper.readValue(oRoutesNode.toString(), oTypeFactory.constructCollectionType(List....
    【解决方案3】:

    这会起作用:

    当您尝试将具有单个元素的列表作为 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 {
        ...
    }
    

    【讨论】:

      【解决方案4】:

      与 Eugen 的回答相关,您可以通过创建一个包含 Collection&lt;COrder&gt; 作为其成员变量的包装 POJO 对象来解决这种特殊情况。这将正确引导 Jackson 将实际的 Collection 数据放入 POJO 的成员变量中,并生成您在 API 请求中查找的 JSON。

      例子:

      public class ApiRequest {
      
         @JsonProperty("collection")
         private Collection<COrder> collection;
      
         // getters
      }
      

      然后将COrderRestService.postOrder()的参数类型设置为您的新ApiRequest包装POJO而不是Collection&lt;COrder&gt;

      【讨论】:

        【解决方案5】:

        如上所述,以下将解决问题: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());
          }
        

        【讨论】:

          【解决方案6】:

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

          我正在寻找一些 REST API 的安全指南,并在 very intriguing issue 中使用了 json 数组。检查链接以获取详细信息,但基本上,您应该将它们包装在一个对象中,正如我们在这个帖子问题中已经看到的那样。

          所以,而不是:

            [
              {
                "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": [
                {...}
              ]
            }
          

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

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

          注意:使用 SpringBoot1.5.5.RELEASE 测试。

          【讨论】:

          • 包装类是正版
          【解决方案7】:

          在这件事上挣扎了太久之后,这里是超级简单的解决方案。

          我的控制器正在寻找

          @RequestBody List<String> ids
          

          我的请求正文为

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

          解决办法就是把body改成

          ["1234", "5678"]
          

          是的。就这么简单。

          【讨论】:

            【解决方案8】:
            @JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
            private List< COrder > orders;
            

            【讨论】:

            • 请提供完整的答案,包含代码块中的一些代码和一些评估答案正确性的文本
            【解决方案9】:

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

            【讨论】:

              【解决方案10】:

              通常,当 JSON 节点与 Java 对象的映射出现问题时,我们会遇到此问题。我遇到了同样的问题,因为在招摇中,节点被定义为类型数组,而 JSON 对象只有一个元素,因此系统难以将一个元素列表映射到数组。

              在 Swagger 中,元素被定义为

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

              应该是这样的

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

              TestNew 应该是数组类型

              【讨论】:

                【解决方案11】:
                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);
                        }
                

                【讨论】:

                  【解决方案12】:

                  同样的问题:

                  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,因此无法自动识别为 application/json 请求,这是服务器的限制 (consumes = "application/json")。一个有效的 JSON 请求将是。修复它的是显式地填充具有空正文和 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);
                  

                  【讨论】:

                    【解决方案13】:

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

                    因此,我在 JSON 文件的开头添加了“[”,在结尾添加了“]”,以将其转换为对象数组,它运行良好,没有任何错误。

                    【讨论】:

                      【解决方案14】:

                      我正在处理同样的问题,我想一次存储多条记录。

                      经过大量研究后,我找到了一个立竿见影的简单解决方案

                      解决问题前代码:

                      控制器方法

                         @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类中实现了Serializable接口如下。

                      实体类

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

                      在这个修改之后,来自邮递员的第一类请求对象起作用了。

                      【讨论】:

                        猜你喜欢
                        • 2021-03-26
                        • 2017-09-22
                        • 2020-02-20
                        • 2018-02-03
                        • 2019-10-16
                        • 1970-01-01
                        • 1970-01-01
                        相关资源
                        最近更新 更多