Dan
Dan

Reputation: 1179

Jackson deserialization issues with arrays and objects

I'm running into a problem where my JSON response can be object or an array of objects

Foobar example with a single value:

{
    "foo": {"msg": "Hello World" }
}

Foobar example with an array:

{
    "foo": [
           { "msg": "Hello World" },
           { "msg": "Goodbye World" }
         ]
}

I want the force the single value into any array but so far, the only way I found converted all single values as arrays.


ACCEPT_SINGLE_VALUE_AS_ARRAY

http://wiki.fasterxml.com/JacksonFeaturesDeserialization


I've been looking around for an annotation that does the same thing for a single property but so far google hasn't turned up any examples.

Has anyone run into this problem before, I really don't want to rewrite everything as arrays to make RestTemplate work with a buggy service.

Upvotes: 1

Views: 7406

Answers (3)

Vicky Kapadia
Vicky Kapadia

Reputation: 6081

Best Way to resolve this when using RestTemplate.

import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
ObjectMapper objectMapper = new ObjectMapper()
            .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
            .configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, false);
MappingJackson2HttpMessageConverter jacksonMappingConverter 
              = new MappingJackson2HttpMessageConverter(objectMapper);
restTemplate.getMessageConverters().add(0, jacksonMappingConverter);

For the element to be parsed use the annotation which can be object or array define as below

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
public class ParentObject{
  @JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
  @JsonProperty("InnerObject")
 private List<InnerObject> innerObject;

}

If you don't want to add new mapper to restTemplate , change the exisitng one to support the use case

List<HttpMessageConverter<?>> messageConverters = restTemplate.getMessageConverters();
        for (HttpMessageConverter<?> httpMessageConverter : messageConverters) {
            if (httpMessageConverter instanceof MappingJackson2HttpMessageConverter) {
                MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = (MappingJackson2HttpMessageConverter) httpMessageConverter;
                mappingJackson2HttpMessageConverter.getObjectMapper()
                        .configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
            }
        } 

Upvotes: 0

Ranil Wijeyratne
Ranil Wijeyratne

Reputation: 777

I had the same issue and struggled finding a solution to generally configure my RestTemplate that way. Because you don't always want to instantiate and alter an objectMapper... So here's my solution:

<bean id="myRestTemplate" class="org.springframework.web.client.RestTemplate">
    <property name="messageConverters">
        <list>
            <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
                <property name="objectMapper" ref="jacksonObjectMapper" />
            </bean>
        </list>
    </property>
</bean>
<bean id="jacksonObjectMapper" class="com.fasterxml.jackson.databind.ObjectMapper" />
<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
    <property name="targetObject" ref="jacksonObjectMapper" />
    <property name="targetMethod" value="configure" />
    <property name="arguments">
        <list>
            <value type="com.fasterxml.jackson.databind.DeserializationFeature">ACCEPT_SINGLE_VALUE_AS_ARRAY</value>
            <value>true</value>
        </list>
    </property>
</bean>

You can then use this pre-configured RestTemplate by injecting it into your code:

@Autowired
private RestTemplate myRestTemplate;

Upvotes: 1

Perception
Perception

Reputation: 80603

I want the force the single value into any array but so far, the only way I found converted all single values as arrays.

This simply shouldn't be the case. The ACCEPT_SINGLE_VALUE_AS_ARRAY property is on/off for a given ObjectMapper, but its behavior is entirely governed by the target property the JSON value is being mapped to.

  • When ACCEPT_SINGLE_VALUE_AS_ARRAY is on, mapping a JSON value to a Java collection property will not result in an error.
  • When ACCEPT_SINGLE_VALUE_AS_ARRAY is on, mapping a JSON value to a Java basic property will (also) not result in an error.

Illustrated by the following code:

class Foo {
    private String msg;

    // Constructor, setters, getters
}

class Holder {
    private List<Foo> foo;
    private Foo other;

    // Constructors, setters, getters
}

public class FooTest {

    @org.junit.Test
    public void testCollectionFromJSONValue() throws Exception {
        final InputStream stream = Thread.currentThread()
                .getContextClassLoader().getResourceAsStream("foo.json");

        final String json = IOUtils.toString(stream);

        final ObjectMapper mapper = new ObjectMapper();
        mapper.configure(
                DeserializationConfig.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY,
                true);
        final Holder holder = mapper.readValue(json, Holder.class);
        System.out.println(holder);
    }
}

Which relies on the following JSON:

{
    "foo": {
        "msg": "Hello World"
    },
    "other": {
        "msg": "Goodbye"
    }
}

Running the code will show that the "foo" property is successfully deserialized into a list, whereas the "other" property gets deserialized into a (basic) Foo type.

Upvotes: 8

Related Questions