Can you set Jackon's Include.NON_NULL globally in JAX-RS? - java

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>

Related

Avoid Jackson xmlMapper being used as default objectMapper with Spring

I had to add this dependency to my pom.xml in order to deserialize xml files in my software.
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
However it seem to now be used as default objectMapper by Spring (4.1.6) and all of the rest response which are not explicitly asked as json are returned as xml.
Seemingly, the AbstractJackson2HttpMessageConverter takes the wrong road.
I tried to force the use of the default object mapper by adding this to the app-config.xml but it did not change anything:
<bean name="jacksonMapper" class="com.fasterxml.jackson.databind.ObjectMapper"/>
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="messageConverters">
<list>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper" ref="jacksonMapper" />
</bean>
</list>
</property>
</bean>
I might have to change jackson for xstream because it does not extend objectMapper and thus doesn't work the same way with Spring, but it would be much more work than setting up Spring.
I finally wound up with some sort of solution whereby I had to introduce two relatively gnarly hacks. But given how hardcoded the problem is in Spring this is the only way I could see to deal with this.
On the Spring MVC side of things I had to extend the WebMvcConfigurerAdapter overriding the following method. This simply takes out the message converter:
public void extendMessageConverters (List<HttpMessageConverter<?>> aConverters)
{
aConverters.removeIf (aConverter -> (aConverter instanceof MappingJackson2XmlHttpMessageConverter));
}
Then to fix the RestTemplate I had to add this to one of my configuration classes. It also simply removes the message converter.
#Autowired
private List<RestTemplate> restTemplates;
#PostConstruct
public void removeXmlMessageConverter ()
{
restTemplates.forEach (aRestTemplate -> aRestTemplate.getMessageConverters ().removeIf (aConverter -> (aConverter instanceof MappingJackson2XmlHttpMessageConverter)));
}
I opted for the removal of the unwanted converter rather than specifying a list of hardcoded message converters as that does not lock me in as much for future upgrades.

jackson serialization views: nested object

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.

Can't configure Jackson mix in

Could you please help me to configure correctly Jackson mix-in annotation with Spring MVC to customize a JSON response.
This is what I have now :
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
<property name="messageConverters">
<list>
<ref bean="jsonMessageConverter"/>
</list>
</property>
</bean>
<bean id="jsonMessageConverter" class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>
I don't know how to configure it to map the 2 classes ... : addMixInAnnotations(User.class, UserMixIn.class);
dd the mixin configuration to the ObjectMapper once initialized in your Controller constructor:
#Controller
public class MyController {
private ObjectMapper objectMapper = new ObjectMapper();
public MyController(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
objectMapper.addMixInAnnotations(User.class, UserMixIn.class);
}
#RequestMapping("/some-path")
#ResponseBody
public String someMethod() {
List<User> users = new ArrayList<User>(); // Mock List to hold your Users
users.add(new User()); // Keep adding some users
return objectMapper.writeValueAsString(users, new TypeReference<List<User>>() {});
}
}
And check out the output :)
The solution given by #tmarwen will work just fine if you only need to configure the Mixin for a single controller.
However if you want to use the mixin thoughout all Spring controllers you need implement an approach similar to the following:
Change your XML configuration to:
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
<property name="messageConverters">
<list>
<ref bean="jsonMessageConverter"/>
</list>
</property>
</bean>
<bean id="jsonMessageConverter" class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper" ref="mapper"/>
</bean>
Next you need to configure the mapper bean that is referenced in the XML above. You could easily do that in XML with the use of SpEL and FactoryBean, but there is no good reason to do so when you have a great and super easy to use alternative in Java Config.
#Configuration
public class JacksonConfig {
#Bean
public ObjectMapper mapper() {
final ObjectMapper mapper = new ObjectMapper();
mapper.addMixInAnnotations(User.class, UserMixIn.class);
return mapper;
}
}
With the changes above in place, you need absolutely no reference to ObjectMapper in your controllers and can use Spring MVC's JSON features just like you are using them now.

jackson spring controller serialization

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.

Why isn't spring using my constructor to init this bean (Map parameter)

I have a spring beans configuration file where I define the following jackson classes as spring beans.
For some reason on run-time the filterProvider bean is instantiated without the map argument.
You can see from the docs that the SimpleFilterProvider does have such a constructor and that SimpleBeanPropertyFilter implements BeanPropertyFilter.
<bean id="productAttributesAndAdvertiserNameFilter" class="org.codehaus.jackson.map.ser.impl.SimpleBeanPropertyFilter" factory-method="filterOutAllExcept">
<constructor-arg value="name"/>
</bean>
<bean id="offerIdFilter" class="org.codehaus.jackson.map.ser.impl.SimpleBeanPropertyFilter" factory-method="filterOutAllExcept">
<constructor-arg value="id"/>
</bean>
<bean id="filterProvider" class="org.codehaus.jackson.map.ser.impl.SimpleFilterProvider">
<constructor-arg>
<util:map value-type="org.codehaus.jackson.map.ser.BeanPropertyFilter">
<entry key="onlyNameFilter" value-ref="productAttributesAndAdvertiserNameFilter" />
<entry key="onlyIdFilter" value-ref="offerIdFilter" />
</util:map>
</constructor-arg>
</bean>
Update:
As of Jackson 1.9.5 this issue is fixed (thanks Tatu)
Any help would be appreciated.
Looks like you've found a bug in SimpleFilterProvider.
I just downloaded the latest sources (1.9.4) and the constructors are defined as such:
public SimpleFilterProvider() {
_filtersById = new HashMap<String,BeanPropertyFilter>();
}
/**
* #param mapping Mapping from id to filter; used as is, no copy is made.
*/
public SimpleFilterProvider(Map<String,BeanPropertyFilter> mapping) {
_filtersById = new HashMap<String,BeanPropertyFilter>();
}
The constructor which takes the mapping ignores it... (i.e. javadoc is incorrect)
I think <util:map> is misplaced here. I'd make it a separate bean, outside of the filter provider declaration, and refer to it. OR I'd change that to a <map> without the util namespace.
I don't see why it is not working.
At worst, you can create your own class by extending the SimpleFilterProvider and declare this bean in your Spring context...

Categories