Can not deserialize instance of java.util.ArrayList out of START_OBJECT token
Asked Answered
M

14

173

I'm trying to POST a List of custom objects. My JSON in request body is this:

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

Server side code that handles the request:

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

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

But an exception is thrown:

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)
Moulding answered 30/12, 2013 at 10:39 Comment(0)
H
190

The problem is the JSON - this cannot, by default, be deserialized into a Collection because it's not actually a JSON Array - that would look like this:

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

Since you're not controlling the exact process of deserialization (RestEasy does) - a first option would be to simply inject the JSON as a String and then take control of the deserialization process:

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

You would loose a bit of the convenience of not having to do that yourself, but you would easily sort out the problem.

Another option - if you cannot change the JSON - would be to construct a wrapper to fit the structure of your JSON input - and use that instead of Collection<COrder>.

Henson answered 30/12, 2013 at 11:41 Comment(5)
Great, I wrapped in collection because there was an example in Resteasy docs, but it was with XML.Moulding
@Moulding nice, can you share that resteasy link here pleasePyrogenous
come to think of it. I'm pretty sure that I have the right code and debugging this for hours, and changing my codes along the way. Turns out I was just missing the square brackets to indicate my post is an array.Arrg. Well I guess this is a newbies curse, LOL. To anyone new to JSON and Spring Data, don't follow my footsteps. :(Aweather
The code is not working for me, in controller what should be the expected code?Flatiron
works flawlessly!Countermarch
G
114

Instead of the JSON document, you can update the ObjectMapper object like below :

ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
Greenfinch answered 2/5, 2018 at 13:12 Comment(1)
This helped me to "ignore" the wrong brackets ... and in Combination mit JsonNode and converting it to String before Jackson-parsing got me running again :-) ... JsonNode oJsonNode = oJacksonObjectMapper.readTree(sJsonString); and JsonNode oRoutesNode = oJsonNode.get("collection"); and mapper.readValue(oRoutesNode.toString(), oTypeFactory.constructCollectionType(List....Badlands
A
16

This will work:

The problem may happen when you're trying to read a list with a single element as a JsonArray rather than a JsonNode or vice versa.

Since you can't know for sure if the returned list contains a single element (so the json looks like this {...}) or multiple elements (and the json looks like this [{...},{...}]) - you'll have to check in runtime the type of the element.

It should look like this:

(Note: in this code sample I'm using 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 {
    ...
}
Agreeable answered 15/5, 2017 at 12:3 Comment(0)
D
16

After struggling with this thing for WAY too long, here is the super easy solution.

My controller was looking for

@RequestBody List<String> ids

and I had the request body as

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

and the solution was to change the body simply to

["1234", "5678"]

Yup. Just that easy.

Declassify answered 29/12, 2020 at 21:37 Comment(0)
M
15

Related to Eugen's answer, you can solve this particular case by creating a wrapper POJO object that contains a Collection<COrder> as its member variable. This will properly guide Jackson to place the actual Collection data inside the POJO's member variable and produce the JSON you are looking for in the API request.

Example:

public class ApiRequest {

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

   // getters
}

Then set the parameter type of COrderRestService.postOrder() to be your new ApiRequest wrapper POJO instead of Collection<COrder>.

Mamba answered 11/4, 2019 at 17:22 Comment(0)
E
7
@JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
private List< COrder > orders;
Empressement answered 24/10, 2019 at 12:54 Comment(2)
Please provide a complete answer with some code inside code blocks and some text evaluating the correctness of your answerClareta
This is exactly the setting I needed, and which I had no idea existed. Thanks so much!Goldschmidt
L
5

As mentioned above the following would solve the problem: mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);

However in my case the provider did this [0..1] or [0..*] serialization rather as a bug and I could not enforce fixing. On the other hand it did not want to impact my strict mapper for all other cases which needs to be validated strictly.

So I did a Jackson NASTY HACK (which should not be copied in general ;-) ), especially because my SingleOrListElement had only few properties to patch:

@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());
  }
Lustrate answered 6/5, 2020 at 9:45 Comment(0)
K
4

I ran into this same problem these days and maybe some more detail might be helpful to somebody else.

I was looking some security guidelines for REST APIs and crossed a very intriguing issue with json arrays. Check the link for details, but basically, you should wrap them within an Object as we already saw in this post question.

So, instead of:

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

It's advisable that we always do:

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

This is pretty straight-forward when you doing a GET, but might give you some trouble if, instead, your trying to POST/PUT the very same json.

In my case, I had more than one GET that was a List and more than one POST/PUT that would receive the very same json.

So what I end up doing was to use a very simple Wrapper object for a List:

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

Serialization of my lists were made with a @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;
  }
}

So, all the Lists and Maps where wrapped over a data object as below:

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

Deserialization was still default, just using 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();
}

That was it! Hope it helps someone.

Note: tested with SpringBoot 1.5.5.RELEASE.

Kyla answered 29/1, 2020 at 16:21 Comment(1)
the wrapper class is genuineInstar
T
2

The error message "Can not Deserialize Instance of java.util.ArrayList Out of start_object Token" typically occurs when you are trying to deserialize a JSON object to an ArrayList in Java, but the JSON data is not in the expected format.

This error message indicates that the JSON data that you are trying to deserialize is starting with a JSON object token {...}, but your code is expecting an array token [...]

Reference: https://devwithus.com/can-not-deserialize-instance-of-java-util-arraylist-start-object-token/

Tripos answered 18/4, 2023 at 16:25 Comment(0)
M
1

Same issue:

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

What caused it was the following:

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

In my test I purposely set the request as null (no content POST). As previously mentioned, the cause for the OP was the same because the request didn't contain a valid JSON, so it couldn't be automatically identified as an application/json request which was the limitation on the server (consumes = "application/json"). A valid JSON request would be. What fixed it was populating an entity with null body and json headers explicitly.

HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity request = new HttpEntity<>(null, headers);
ResponseEntity<UUID> response = restTemplate.postForEntity("/example/", request, UUID.class);
Maltose answered 27/7, 2019 at 14:47 Comment(0)
O
0

I had this issue on a REST API that was created using Spring framework. Adding a @ResponseBody annotation (to make the response JSON) resolved it.

Orthogenetic answered 16/7, 2018 at 21:23 Comment(0)
R
0

Normally we face this issue when there is a problem mapping JSON node with that of Java object. I faced the same issue because in the swagger the node was defined as of Type array and the JSON object was having only one element , hence the system was having difficulty in mapping one element list to an array.

In Swagger the element was defined as

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

While it should be

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

And TestNew should be of type array

Resign answered 11/1, 2019 at 20:54 Comment(0)
K
0

In my case, the error was being shown because when I was reading my JSON file using Jackson library, my JSON file contained only 1 object. Hence it started with "{" and ended with "}". But while reading it and storing it in a variable, I was storing it in an Array object (As in my case, there could be more than 1 object).

Hence, I added "[" in the start and "]" in the end of my JSON file to convert it into an array of object and it worked perfectly fine without any error.

Kimsey answered 26/12, 2019 at 13:26 Comment(0)
C
0

I was working with the same issue, I wanted to store multiple records at once.

After a lot of research, I found an easy solution that worked immediately.

Before solving the problem code:

Controller method

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

Service method

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

Entity class

   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;

}

postman object

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

and another format

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

In both methods, it was giving an error of serialization/deserialization.

then I just implemented the Serializable interface in the Entity class as follows.

Entity class

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

After this modification the first type of request object from postman worked.

Congreve answered 4/11, 2021 at 9:4 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.