Reputation: 1179
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
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
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
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.
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