I have a Java Spring 3.2 + Hibernate project.
I used jackson2 annotations (com.fasterxml.jackson.annotation) in the model, and I (guess) the spring rest controller should use jackson2 (aka com.fasterxml.jackson) when serializing the requested objects.
I configured the application with:
<!-- Use the HibernateAware mapper instead of the default -->
<bean
class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper">
<bean class="myProj.util.HibernateAwareObjectMapper">
<property name="serializationInclusion">
<value type="com.fasterxml.jackson.annotation.JsonInclude.Include">NON_NULL</value>
</property>
</bean>
</property>
</bean>
</mvc:message-converters>
The HibernateAwareObjectMapper is defined this way:
package myProj.util;
import com.fasterxml.jackson.databind.ObjectMapper;
public class HibernateAwareObjectMapper extends ObjectMapper {
private static final long serialVersionUID = -5002954669679467811L;
public HibernateAwareObjectMapper() {
Hibernate4Module hbm = new Hibernate4Module();
hbm.enable(Hibernate4Module.Feature.FORCE_LAZY_LOADING);
registerModule(hbm);
}
}
so I can state that it extends the com.fasterxml ObjectMapper (OTOH I'm not sure why it was added, since I just inherited the code from other developers).
Note that from what I know spring3.2 should use jackson2 by default.
This is mostly working fine but then I have a serialization issue which only happens with a specific service/controller. I have an object which defines a parent, containing the same object as a child. This is resulting in a serialization loop, which ends with an exception on the server side:
[...]
at org.codehaus.jackson.map.ser.BeanSerializer.serialize(BeanSerializer.java:112)
at org.codehaus.jackson.map.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:446)
at org.codehaus.jackson.map.ser.BeanSerializer.serialize(BeanSerializer.java:112)
at org.codehaus.jackson.map.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:446)
at org.codehaus.jackson.map.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:150)
at org.codehaus.jackson.map.ser.BeanSerializer.serialize(BeanSerializer.java:112)
at org.codehaus.jackson.map.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:446)
at org.codehaus.jackson.map.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:150)
[...]
and an incomplete JSON sent to the client.
This is the controller code:
#RequestMapping(value = "/getReleases", method = RequestMethod.POST)
public Map<String, Object> getReleases(#RequestBody Project project) {
Map<String, Object> subProjectsMap = new HashMap<String, Object>();
List<Release> releaseList = null;
try {
releaseList = jiraService.getReleases(project);
subProjectsMap.put("success", (releaseList.size() > 0) ? true : false);
subProjectsMap.put("data", releaseList);
} catch (Exception e) {
e.printStackTrace();
}
return subProjectsMap;
}
Serialization is performed implicitly by the framework.
The question is: why is spring apparently using org.codehaus.jackson rather than com.fasterxml.jackson as I would expect? Note that the model describing the serialized object is using jackson2 annotations (in particular #JsonBackReference and #JsonIgnore), so they are possibly ignored when using jackson1, which (I think) may result in the loop issue.
After many hours spent banging my head I still don't know why this is happening. Can you provide any hint?
Well, it turned out the problem was due to the missing #ResponseBody annotation in the controller code.
#RequestMapping(value = "/getReleases", method = RequestMethod.POST)
public #ResponseBody Map<String, Object> getReleases(#RequestBody Project project) {
[...]
}
Adding the #ResponseBody annotation magically fixed the problem for me.
I realized the problem by comparing this controller code with the code from similar controllers which were not showing the issue (silly of me not doing this before), after several vain attempts at digging through the Spring code.
Thanks all for the answers!
Spring automatically configures MappingJackson2HttpMessageConverter when Jackson2 is on the classpath. However, you override this behavior by defining your own bean of type MappingJackson2HttpMessageConverter. First of all, check if it is your bean being used for serialization (e.g. via debugging). Then check its configuration. It seems the constructor of the Hibernate extension to ObjectMapper is not calling super(), which means that configuration of the default SerializerProvider and BeanSerializerFactory is missing. See http://fasterxml.github.io/jackson-databind/javadoc/2.0.0/com/fasterxml/jackson/databind/ObjectMapper.html for more details.
Related
This is my first post, so please excuse me if I miss to follow any convention of posting a good question.
I am coding a RESTful service using Spring REST. The signature of the service is as below.
#RestController
#RequestMapping(value = "/{param1}/{param2}/myservice", produces = MediaType.APPLICATION_JSON_VALUE)
public class MyService {
#RequestMapping(value = "/{param3}", method = GET)
public MyResponseObj getMyResponseDetails(MyRequestObject request){
//Service logic code
}
}
In above example, MyRequestObject contains a custom date object with a specific format. For the reference of this question, the name of the custom date object of MyDate in MyRequestObject is myDate.
My objective here is to convert the string value of myDate coming as a query parameter with name myDate. I have purposefully kept the query parameter name the same as of the inner object name within MyRequestObject class, so that Spring can implicitly assign the value to the myDate attribute of the MyRequestObject instance of the method argument. To make this auto assignment, I ensured to keep a constructor for MyDate with a String parameter. But, Spring does not do this auto-assignment for myDate value.
So, after reading several posts on this forum, I created a custom converter to convert a String object to MyDate object. Below is my code for the same.
#Component
public class StringToMyDateConverter implements Converter<String, MyDate> {
public MyDate convert(String s){
//MyDate.parseData(String) is a custom method to convert a String to MyDate object
return MyDate.parseData(s);
}
}
Then, I had added below configuration into my Spring config.xml file to add this custom converter class into Spring's default conversion service.
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="com.my.service.rest.util.conversion.StringToMyDateConverter"/>
</set>
</property>
</bean>
During application startup while debug, I have found that my custom converter code is getting executed and the Spring's default converter service registers my custom converter. However, I have also noticed that the Spring's internal code to initialize FormattingConversionServiceFactoryBean is getting executed multiple times during start up of the service. And that eventually resulted into overwriting of the converter service listing, which did not have my custom converter that got registered before.
So, when I invoke the REST service URL mentioned below, I get below mentioned error.
URL
http://localhost:7880/project/json/value1/value2/myservice/value3?myDate=2017-09-12
Exception
org.springframework.validation.BindException:
org.springframework.validation.BeanPropertyBindingResult: 1 errors
Field error in object 'request' on field 'myDate': rejected value [2017-09-12]; codes [typeMismatch.request.myDate,typeMismatch.myDate,typeMismatch.com.my.service.xml.datatype.MyDate,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [request.myDate,myDate]; arguments []; default message [myDate]]; default message [Failed to convert property value of type [java.lang.String] to required type [com.my.service.xml.datatype.MyDate] for property 'myDate'; nested exception is java.lang.IllegalStateException: Cannot convert value of type [java.lang.String] to required type [com.my.service.xml.datatype.MyDate] for property 'myDate': no matching editors or conversion strategy found]
It is important to mention here that my project has a complex module structure having multiple Spring configurations.
I think, because of that Spring is initializing the default conversion service multiple times. Though, I am not able to determine the order of configuration invocations so that I can prevent Spring overwriting my customer configuration added in the list. I have also tried the annotation based configuration class as listed below to achieve this result. But that also resulted in same issue.
#Configuration
public class ConversionServiceProvider {
#Autowired
private StringToMyDateConverter stringToMyDateConverter;
#Bean
public ConversionService getConversionService() {
ConversionServiceFactoryBean bean = new ConversionServiceFactoryBean();
bean.setConverters(getConverters());
bean.afterPropertiesSet();
ConversionService object = bean.getObject();
return object;
}
private Set<Converter<?, ?>> getConverters() {
Set<Converter<?, ?>> converters = new HashSet<>();
converters.add(stringToMyDateConverter);
// add here more custom converters, either as spring bean references or directly instantiated
return converters;
}
}
Can you please suggest me anything that I can try here to solve this issue? Please excuse me for the lengthy question, but I think that would help to understand the situation and problem better.
Thanks!!
The following is a snippet from a project based on Spring Boot 1.3. Json serialization is made via Jackson 2.6.3.
I have the following Spring MVC controller:
#RequestMapping(value = "/endpoint", method = RequestMethod.GET, produces=MediaType.APPLICATION_JSON_VALUE)
public Results<Account> getAllAccounts() throws Exception {
return service.getAllAccounts();
}
The returned Results is as follows (getters and setters removed):
public class APIResults<T> {
private Collection<T> data;
}
The Account class is as follows (getters and setters removed):
public class Account {
#JsonView(Views.ViewA.class)
private Long id;
#JsonView(Views.ViewB.class)
private String name;
private String innerName;
}
I also have the following Json Views
public class Views {
public interface ViewA {}
public interface Publisher extends ViewB {}
}
The motive is to return different view from the same controller, based on some predict.
So I'm using AbstractMappingJacksonResponseBodyAdvice to set the view at run-time. When setting bodyContainer.setSerializationView(Views.ViewA.class), I'm getting an empty json result and not a json array of objects that only contains the id property. I suspect this is because the data property in APIResults< T > is not annotated with #JsonView, however shouldn't non annotated properties be included in all views (doc)?
Is there a way to get what i want without adding the #JsonView annotation to APIResults< T > (this is not an option).
I know i can use Jackson mixin to get the functionality i desire, however is there a way to do that using Json views ?
You were right, spring MVC Jackson2ObjectMapperBuilder has been improved to set jackson mapper feature DEFAULT_VIEW_INCLUSION to false, so not annotated property data is not serialized.
To enable the feature, use following config (XML):
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper">
<bean class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean">
<property name="defaultViewInclusion" value="true"/>
</bean>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
With this config, you'll have id and innerName properties serialized when using ViewA view:
{"data":[
{"id":1,"innerName":"one"},
{"id":2,"innerName":"two"},
{"id":3,"innerName":"three"}
]}
Further, you may manage to hide innerName also, by adding some other view annotation to it.
I have a JAX-RS API using Apache CXF. Recently I switched from Jettison to Jackson (2.7.1) for JSON handling. I am using JacksonJaxbJsonProvider.
One thing I need to do to help with transition is to ensure that null fields in JSON are not being rendered. For that I am using following annotation on class level:
#JsonInclude(JsonInclude.Include.NON_NULL)
public class Book {
...
}
Is there any way to set it globally, so that I do not have to apply this annotation to every class?
With Jettison I was able to specify properties in beans.xml file, is there a similar approach possible with Jackson to achieve NON_NULL behavior?
<bean id="jsonProvider" class="org.apache.cxf.jaxrs.provider.json.JSONProvider">
<property name="dropRootElement" value="true"/>
<property name="supportUnwrapped" value="true"/>
</bean>
I have seen some suggestion to set up this property on ObjectMapper level when instantiated, however when using Apache CXF I never create ObjectMapper by hand, as this instantiation is handled by the framework (probably happens somewhere in JacksonJaxbJsonProvider).
Is there any way to set NON_NULL property globally?
Just in case it helps somebody else, configuring ObjectMapper in beans.xml worked with NON_NULL however my JAXB annotations stopped working. To get both of them working at the same time I resorted to creating my own ObjectMapper provider:
#Provider
public class CustomJacksonObjectMapperProvider implements ContextResolver<ObjectMapper> {
final ObjectMapper defaultObjectMapper;
public CustomJacksonObjectMapperProvider() {
defaultObjectMapper = createDefaultMapper();
}
#Override
public ObjectMapper getContext(Class<?> type) {
return (defaultObjectMapper);
}
private static ObjectMapper createDefaultMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationInclusion(Include.NON_NULL);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.setAnnotationIntrospector(
AnnotationIntrospector.pair(
new JacksonAnnotationIntrospector(),
new JaxbAnnotationIntrospector(mapper.getTypeFactory())
));
return (mapper);
}
}
and registering it in beans.xml (under jaxrs:providers) as follows:
<bean id="customJacksonObjectMapperProvider" class="CustomJacksonObjectMapperProvider"/>
You can include the following configuration as below:
<bean id="jacksonJaxbJsonProvider"
class="com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider">
<constructor-arg ref="objectMapper"></constructor-arg>
</bean>
<bean id="objectMapper" class="com.fasterxml.jackson.databind.ObjectMapper">
<property name="serializationInclusion" value="NON_NULL"></property>
</bean>
I am downloading a json file and contents are available as arrays of arrays.
There are various validation(notNull, isNumber etc) which I want to do for the elements of child array.
One was is when I am using Spring batch and enable ValidatingItemProcessor and Bean Validation works for me.
However, I want to write a standalone solution using already existing Validator frameworks like from Apache but do not want to validate as bean but directly on array.
What should be my approach to this problem.
I am using Spring framework, so anything around that would be helpful.
We used JSR validations for our spring batch application. We annotated our classes with validations such as #NotNull,#DecimalMin. We then created a CommonValidator such as
import javax.validation.Validator;
...
public class CommonValidator<T> implements ItemProcessor<T, T>{
#Autowired
private Validator validator;
public T process(T t) throws Exception{
Set<ConstraintViolation<T>> constraintViolations = validator.validate(t);
return constraintViolations.isEmpty()? t : null;
}
We then, added it in a CompositeItemProcessor as follows.
<bean id="validateProcessor" class="mypackage.CommonValidator" />
class="org.springframework..CompositeItemProcessor">
<property name="delegates">
<list>
<ref bean="validateProcessor"/>
<ref bean="otherProcessor"/>
And it worked. On the similar lines, you can write your own validator to validate your array. So, if the array values are valid, then the array is returned. null is returned for an invalid array.
What is the easiest way to bind a UUID in Spring MVC, such that this works:
#RequestMapping("/MyController.myAction.mvc")
#ResponseBody
public String myAction(UUID id, String myParam)...
Using the above I currentely get the following exception:
org.springframework.beans.BeanInstantiationException:
Could not instantiate bean class [java.util.UUID]:
No default constructor found;
nested exception is java.lang.NoSuchMethodException: java.util.UUID.<init>()
There are other questions on SO that skirt around this, but none seem to answer it. I'm using Spring 3.latest (4 EA actually). I'm after the latest, simplest way to achieve this.
UUID is a class that cannot simply be instantiated. Assuming that it comes as a request parameter you should first annotate the argument with #RequestParam.
#RequestMapping("/MyController.myAction.mvc")
#ResponseBody
public String myAction(#RequestParam UUID id, String myParam)...
Now this expects a request parameter with the name id to be available in the request. The parameter will be converted to a UUID by the StringToUUIDConverter which is automatically registered by Spring.
Prior to the Spring 3.2
there was no StringToUUIDConverter so additionally you have to write and register converter by your own.
public class StringToUUIDConverter implements Converter<String, UUID> {
public UUID convert(String source) {
return UUID.fromString(source);
}
}
Hook this class up to the ConversionService and you should have UUID conversion for request parameters. (This would also work if it was a request header, basically for everything that taps into the ConversionService). You also might want to have a Converter for the other-way (UUID -> String).
Hooking it up to Spring MVC is nicely explained in the reference guide (assuming you use xml config). But in short:
<mvc:annotation-driven conversion-service="conversionService"/>
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="org.company.converter.StringToUUIDConverter"/>
</set>
</property>
</bean>
The converter below is available in Spring Framework (core) since version 3.2.
org.springframework.core.convert.support.StringToUUIDConverter<String, java.util.UUID>
If its coming as a Header parameter use
#RequestHeader(value="UUID") String id
If its coming in a model
#ModelAttribute(value="ModelName") Entity modelName