Search code examples
javaspringjacksonresttemplate

restTemplate.getForEntity when reading data that contains a list with single element gives Exception


I have a Detail object that has a property of object of type Product. Product has a property called xxx which is an arraylist. I do a GET on the URL with postman and the result looks like:

"Product": {
    "id": "2",
    "xxx": [
        "price": "50"
      },
      {
        "price": "60"
      }
    ]
  }

This result is good. But, in my Spring project, when I do a get Using RestTemplate as:

  restTemplate.getForEntity("someurl", Detail.class).getBody();

I get correct results when xxx list contains 2 or more element. However, when there is only element in this list, I get an error:

org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Can not deserialize instance of java.util.ArrayList out of START_OBJECT token; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of java.util.ArrayList out of START_OBJECT token

  How do I fix this issue that I am facing with my call to restTemplate.getForEntity as above?

Solution

  • I suspect that your encounter this error not when your list contains one item but rather when you have no list at all but a plain object, thus the parser complains about a misplaced START_OBJECT token. To remedy this and without being able to edit Swagger-generated domain classes, you could set the deserialization feature

    ObjectMapper objectMapper = new ObjectMapper(); // maybe injected
    objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
    

    directly on the ObjectMapper that you are using.

    This object mapper can now be put into the RestTemplate config:

    @RunWith(SpringRunner.class)
    @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
    public class JsonControllerIT {
        @LocalServerPort
        private int port;
    
        @Test
        public void jsonWithSerializationFeatureSet() {
            // given
            RestTemplate restTemplate = new RestTemplate();
            MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
            ObjectMapper objectMapper = new ObjectMapper();
            objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
            messageConverter.setObjectMapper(objectMapper);
            restTemplate.getMessageConverters().removeIf(m -> m.getClass().getName().equals(MappingJackson2HttpMessageConverter.class.getName()));
            restTemplate.getMessageConverters().add(messageConverter);
    
            // when
            Detail detail = restTemplate.getForEntity("http://localhost:" + port + "/json", Detail.class).getBody();
    
            // then
            assertThat(detail.getSingleItemList().get(0)).isEqualTo(3);
        }
    }
    

    If you use the Spring-approach keep in mind that you also can inject all those beans, they are created here straight-forward just show them better together.

    You can also check this running example: