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.
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.
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;
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);
}
}
Related
I am using dozer version 5.5.1. And i want to configure my custom converter so i have this
import org.dozer.DozerConverter;
import com.example.movies.api.models.response.ClientResponseDTO;
public class MyCustomConverter
extends DozerConverter<ClientResponseDTO, String> {
public MyCustomConverter() {
super(ClientResponseDTO.class, String.class);
}
#Override
public String convertTo(ClientResponseDTO source, String destination) {
return "ClientResponseDTO Converted to string!";
}
#Override
public ClientResponseDTO convertFrom(String source, ClientResponseDTO destination) {
return new ClientResponseDTO();
}
}
Which i am loading with Spring like this:
#Bean
public Mapper dozerBeanMapper() {
DozerBeanMapper mapper = new DozerBeanMapper();
List<CustomConverter> converters = new ArrayList<>();
converters.add(new MyCustomConverter(ClientResponseDTO.class, String.class));
mapper.setCustomConverters(converters);
return mapper;
}
Then, i have this usage:
#Autowired Mapper mapper;
...
ClientResponseDTO clientResponseDTO = clientService.getClient(id);
String conversion = this.mapper.map(clientResponseDTO, String.class);
And the custom converter is never being called. Do you know why is that ? Regards!
Refer to dozer documentation you should add mapping to bean description.
Eg.
<bean id="org.dozer.Mapper" class="org.dozer.DozerBeanMapper">
<property name="mappingFiles">
<list>
<value>systempropertymapping1.xml</value>
<value>dozerBeanMapping.xml</value>
<value>injectedCustomConverter.xml</value>
</list>
</property><property name="customConvertersWithId">
<map>
<entry key="CustomConverterWithId" ref="configurableConverterBeanInstance1" />
<entry key="CustomConverterWithId2" ref="configurableConverterBeanInstance2" />
</map>
</property>
</bean>
I want my string method to return value. The below code returns a view(jsp). In the code "line" stores the product details.I want the method to return this value. Could someone show me how to do it.
#RequestMapping(value="addtocart{id}")
#ResponseBody
public String addToCart(#PathVariable("id") int id, #ModelAttribute("cart") Cart cart)
{
Product product = productService.getProductById(id);
if (product != null) {
CartLine line = new CartLine();
line.setProduct(product);
line.setQuantity(1);
return "viewcart";
}
xml config
<bean id="jsonConverter" class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
<property name="prefixJson" value="false"/>
<property name="supportedMediaTypes" value="application/json"/>
</bean>
Just change the return type of the controller method to Product and return the Product instance.
Dependending on what HttpMessageConverters you have configured, the response will be the object serialized to JSON for example.
Update
You can see how to configure the Jackson converter here or in Spring MVC documentation.
OK, so if I need to put some primitive values in the constructor, how do I do that?
#Autowired
public CustomBean(String name, #Qualifier("SuperBean") SuperBean superBean) {
super();
this.superBean = superBean;
this.name = name;
}
For instance here I am defining that the superBean has the Qualifier "SuperBean", but I'd also like to know how is it possible to use annotations to set the name value here?
I know it's possible with xml configuration, but I want to know how to do this with annotations too:
<bean id="CustomXmlBean" class="org.arturas.summerfav.beans.CustomXmlBean">
<constructor-arg name="name" type="String" value="The Big Custom XML Bean" />
<constructor-arg>
<bean id="SuperBean" class="org.arturas.summerfav.beans.SuperBean" />
</constructor-arg>
</bean>
Well how do I put in values for String, int and other generic types?
Here is one way to do this:
#Component
public class YourBean {
#Autowired
public YourBean(#Value("${prop1}") String arg1, #Value("${prop2}") String arg2) {
// rest of the code
}
}
I'm developing a REST webservice in spring MVC. I need to change how jackson 2 serialize mongodb objectids. I'm not sure of what to do because I found partial documentation for jackson 2, what I did is to create a custom serializer:
public class ObjectIdSerializer extends JsonSerializer<ObjectId> {
#Override
public void serialize(ObjectId value, JsonGenerator jsonGen,
SerializerProvider provider) throws IOException,
JsonProcessingException {
jsonGen.writeString(value.toString());
}
}
Create a ObjectMapper
public class CustomObjectMapper extends ObjectMapper {
public CustomObjectMapper() {
SimpleModule module = new SimpleModule("ObjectIdmodule");
module.addSerializer(ObjectId.class, new ObjectIdSerializer());
this.registerModule(module);
}
}
and then register the mapper
<mvc:annotation-driven>
<mvc:message-converters register-defaults="true">
<bean
class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper">
<bean class="my.package.CustomObjectMapper"></bean>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
My CustomConverter is never called. I think the CustomObjectMapper definition is wrong,I adapted it from some code for jackson 1.x
In my controllers I'm using #ResponseBody.
Where am I doing wrong? Thanks
You should annotate corresponding model field with #JsonSerialize annontation. In your case it may be:
public class MyMongoModel{
#JsonSerialize(using=ObjectIdSerializer.class)
private ObjectId id;
}
But in my opinion, it should better don't use entity models as VOs. Better way is to have different models and map between them.
You can find my example project here (I used date serialization with Spring 3 and Jackson 2 as example).
How I would do this is:
Create an annotation to declare your custom serializers:
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface MyMessageConverter{
}
Set up component scan for this in your mvcconfiguration file
<context:include-filter expression="package.package.MyMessageConverter"
type="annotation" />
and create a class that implements HttpMessageConverter<T>.
#MyMessageConverter
public MyConverter implements HttpMessageConverter<T>{
//do everything that's required for conversion.
}
Create a class that extends AnnotationMethodHandlerAdapter implements InitializingBean.
public MyAnnotationHandler extends AnnotationMethodHandlerAdapter implements InitializingBean{
//Do the stuffs you need to configure the converters
//Scan for your beans that have your specific annotation
//get the list of already registered message converters
//I think the list may be immutable. So, create a new list, including all of the currently configured message converters and add your own.
//Then, set the list back into the "setMessageConverters" method.
}
I believe this is everything that is required for your goal.
Cheers.
There is no need to create object mapper. Add jackson-core-2.0.0.jar and jackson-annotations-2.0.0.jar to your project.
Now, add the following lines of code to your controller while handing the service:
#RequestMapping(value = "students", method = RequestMethod.POST, headers = "Accept=application/json", consumes = "application/json")
public HashMap<String, String> postStudentForm(
#RequestBody Student student, HttpServletResponse response)
Do not miss any of the annotations.
I'm trying to add some Spring configuration to an existing utility class. It doesn't seem to work and I'm not sure why (my first time using these Spring options, I'm not even sure I'm doing it correctly).
The class in question
#Configurable(autowire=Autowire.BY_NAME, preConstruction=true)
public class DataUtility
{
private static final DataUtility INSTANCE = new DataUtility();
#Autowired(required=true) //This is the new field and annotation
private Map<String,String> dataFileMapping = new HashMap<String, String>();
public static DataUtility getInstance()
{
return INSTANCE;
}
private DataUtility()
{
//Do a bunch of setup work here
for (String s : dataFileMapping)
{
addDataToCache(dataFileMapping(s))
}
}
The spring config looks like this:
<context:annotation-config/>
<context:spring-configured/>
<bean id="util" class="com.myCompany.DataUtility">
<property name="dataFileMapping">
<map>
<entry key="data1" value="data/file1.dat"/>
<entry key="data2" value="data/file2.dat"/>
<entry key="data3" value="data/file3.dat"/>
</map>
</property>
</bean>
The problem is that when I step through my code in the debugger, I can see that dataFileMapping is empty. I'm not even sure if the spring config is even running.
I think you just need to add getters and setters for dataFileMapping
Also, remember that you can't iterate through the map in the constructor, spring wouldn't have had a chance to set it until after the constructor executes.
In addition to this, you can't make your constructor private and expect spring to be able to instantiate it.
The root of your problem is that you seem to be using a static reference INSTANCE to access the object. Spring is making a bean named 'util' and setting it up with your data, but that isn't becoming the object that INSTANCE points to. The initialization of static fields happens when the class is first loaded, long before spring ever gets a chance to create and inject beans.
You can sort of fake it like this, but of course attempts to access instance before bean initialization will fail:
#Configurable(autowire=Autowire.BY_NAME, preConstruction=true)
public class DataUtility
{
private static final DataUtility INSTANCE = null;
#Autowired(required=true) //This is the new field and annotation
private Map<String,String> dataFileMapping = new HashMap<String, String>();
public static DataUtility getInstance()
{
return INSTANCE;
}
public postInit()
{
INSTANCE = this;
//Do a bunch of setup work here
for (String s : dataFileMapping)
{
addDataToCache(dataFileMapping(s))
}
}
<bean id="util" class="com.myCompany.DataUtility" init-method="postInit">
<property name="dataFileMapping">
<map>
<entry key="data1" value="data/file1.dat"/>
<entry key="data2" value="data/file2.dat"/>
<entry key="data3" value="data/file3.dat"/>
</map>
</property>
</bean>