Registrer MappingJackson2HttpMessageConverter in Spring 3.1.2 with JAXB annotations - json

I have a number of entities with JAXB annotations that I would like to convert to JSON using a message-converter.
I know that my ObjectMapper that reads the JAXB annotations works:
String correctJsonText = jacksonObjectMapper.writeValueAsString(entityWithJAXB);
But when i call my rest service the default registered MappingJacksonHttpMessageConverter (which is not configured for reading JAXB) seems to take over - resulting in a stackoverflow due to cyclic references when #XmlTransient is ignored...
How do i configure Spring to use MappingJackson2HttpMessageConverter ?
Current Configuration
<mvc:annotation-driven>
<mvc:message-converters register-defaults="false">
<bean id="jacksonMessageConverter" class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper" ref="jacksonObjectMapper" />
<property name="supportedMediaTypes">
<list>
<bean class="org.springframework.http.MediaType">
<constructor-arg index="0" value="application" />
<constructor-arg index="1" value="json" />
<constructor-arg index="2" value="UTF-8" />
</bean>
</list>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
<bean id="jaxbAnnotationInspector" class="com.fasterxml.jackson.module.jaxb.JaxbAnnotationIntrospector" />
<bean id="jacksonObjectMapper" class="com.fasterxml.jackson.databind.ObjectMapper">
<property name="annotationIntrospector" ref="jaxbAnnotationInspector" />
</bean>
REST service
#RequestMapping(value="/{id}", method=RequestMethod.GET, produces = "application/json;charset=UTF-8")
public #ResponseBody EntityWithJAXB readEntityWithJAXB(#PathVariable int id, Model model) {
return entityService.getById(id);
}
Dependencies
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.0.5</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.0.5</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-jaxb-annotations</artifactId>
<version>2.0.5</version>
</dependency>
UPDATE / Solution
By debugging my context I could see that the configuration in <mvc:annotation-driven> had no effect.
It turned out that my unit test with MockMcv always loaded the default handleradapters, thus ignoring my custom objectmapper. Due to convenience I only tested the controller using junit test, since it hit the controller just fine I did not think of this as a probable error cause...
I did not find a fix for my test yet, but when I call the service using curl everything works!
UPDATE / Final solution
Just found a solution for my test setup; when using MockMvc (spring-test-mvc) you must specify custom message-converters explicit:
private MockMvc mockMvc;
#Autowired
private MappingJackson2HttpMessageConverter jacksonMessageConverter;
#Autowired
private RestController restController;
#Before
public void initMockMvc(){
mockMvc = MockMvcBuilders.standaloneSetup(restController)
.setMessageConverters(jacksonMessageConverter).build();
}
#Test
public void testRestController() throws Exception{
DefaultRequestBuilder requestBuilder = MockMvcRequestBuilders.get("/json/42");
this.mockMvc.perform(requestBuilder).andDo(print()).andExpect(status().isOk());
}
The only remaining issue is that the jacksonMessageConverter can't be autowired directly until the following JIRA is resolved: https://jira.springsource.org/browse/SPR-9469. Until then, I have just created a copy of the jacksonMessageConverter in my test context.

I replicated your configuration and it works perfectly for me. The default message converters should not take effect, as you have explicitly specified the register-defaults=false attribute. This is how my #XmlTransient annotation looks:
#XmlAccessorType(XmlAccessType.FIELD)
public class EntityWithJAXB {
#XmlTransient
private EntityWithJAXB aChild;

Related

Adding LocalDate Mapper to message-converters in spring

Can someone help me here please ?
I need to convert Java 8 LocalDate to JSON and vice versa in my REST application to MVC application where I am calling REST API's. SInce default JacksonJAXbJSONProvider does not parse LOcalDate, I wrote custom Mapper as below
And dependency is added in pom.xml
public class LocalDateObjectMapperContextResolver implements ContextResolver<ObjectMapper>{
private final ObjectMapper MAPPER;
public LocalDateObjectMapperContextResolver() {
MAPPER = new ObjectMapper();
MAPPER.registerModule(new JavaTimeModule());
//MAPPER.findAndRegisterModules();
MAPPER.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
}
#Override
public ObjectMapper getContext(Class<?> type) {
return MAPPER;
}
}
This was suggested in
Java 8 LocalDate Jackson format
I need to pass it to message-converters in spring.xml.
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"
p:objectMapper-ref="objectMapper"/>
</mvc:message-converters>
</mvc:annotation-driven>
Since one default mapper is already there, I cannot add my mapper to it. So I made below changes, but it still does not work
<mvc:annotation-driven >
<mvc:message-converters register-defaults="false">
<bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter" >
<!-- p:objectMapper-ref="objectMapper"/> -->
<property name="objectMapper" ref="localDateMapper" />
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
This is obviously not correct since I am passing resolver where mapper is required. How do I call getContext() method of custom class and set that in Message Converter ?
Now it started throwing exception whilel starting application
Error creating bean with name 'org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#0': Cannot create inner bean 'org.springframework.http.converter.json.MappingJacksonHttpMessageConverter#0' of type [org.springframework.http.converter.json.MappingJacksonHttpMessageConverter] while setting bean property 'messageConverters' with key [0]; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.http.converter.json.MappingJacksonHttpMessageConverter#0' defined in ServletContext resource [/WEB-INF/spring.xml]: Initialization of bean failed; nested exception is org.springframework.beans.ConversionNotSupportedException: Failed to convert property value of type '****package***.LocalDateObjectMapperContextResolver' to required type 'org.codehaus.jackson.map.ObjectMapper' for property 'objectMapper'; nested exception is java.lang.IllegalStateException: Cannot convert value of type [****package***.LocalDateObjectMapperContextResolver] to required type [org.codehaus.jackson.map.ObjectMapper] for property 'objectMapper': no matching editors or conversion strategy found
You need only a custom module, not a mapper. Then configure spring accordingly:
<bean id="objectMapper" class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean"
p:simpleDateFormat="yyyy-MM-dd"
p:modulesToInstall="your.module.JavaTimeModule"/>
<bean id="xmlMapper" parent="objectMapper" p:createXmlMapper="true"/>
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper" ref="objectMapper"/>
</bean>
<bean class="org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter">
<property name="objectMapper" ref="xmlMapper"/>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>

Custom ObjectMapper and NamingStrategy in Spring 3 MVC

I'm using Spring MVC 3 and MappingJacksonHttpMessageConverter in order to get the json data with #ResponseBody. With the default config works ok but now i need to transform the camelCase fields to Pascal casing. For this purpose, i've developed a custom naming strategy:
UpperCaseNamingStrategy.java
public class UpperCaseNamingStrategy extends PropertyNamingStrategy {
#Override
public String nameForField(MapperConfig config, AnnotatedField field, String defaultName){
return convert(defaultName);
}
#Override
public String nameForGetterMethod(MapperConfig config, AnnotatedMethod method, String defaultName){
return convert(defaultName);
}
#Override
public String nameForSetterMethod(MapperConfig config, AnnotatedMethod method, String defaultName){
return convert(defaultName);
}
public String convert(String defaultName){
char[] arr= defaultName.toCharArray();
if(arr.length != 0){
if(Character.isLowerCase(arr[0])){
arr[0] = Character.toUpperCase(arr[0]);
}
}
return new StringBuilder().append(arr).toString();
}
}
I set my custom strategy to the objectMapper and i set the objectMapper in the converter. These are the beans:
<bean id="jacksonMessageConverter" class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
<property name="objectMapper" ref="jacksonObjectMapper" />
</bean>
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="messageConverters">
<list>
<ref bean="jacksonMessageConverter"/>
</list>
</property>
</bean>
<bean id="jacksonObjectMapper" class="org.codehaus.jackson.map.ObjectMapper">
<property name="propertyNamingStrategy" ref="namingStrategy"/>
</bean>
<bean id="namingStrategy" class="es.unican.meteo.util.UpperCaseNamingStrategy"></bean>
The beans are registered properly because i can see it in the log but when i request the json data the behaviour is the same and the converter method is not called. Do I need more configs?
Following changes are suggested as compared to what I did in my project:
Change mapper bean class to "com.fasterxml.jackson.databind.ObjectMapper". I am using Spring 4.3
add #JsonProperty annotation to the property of class which is being serielized/deseralized
Create default constructors in class which is being serielized/deseralized
Best of Luck!

Jackson JSON not working with CXF

The JacksonJsonProvider is not working with CXF.
CXF v2.6.0
Jackson v2.1.2 (com.fasterxml.jackson)
RESTClient (for testing)
I do have the provider configured like below in beans.xml.
<bean id="jacksonMapper" class="com.fasterxml.jackson.databind.ObjectMapper">
<property name="dateFormat">
<bean class="java.text.SimpleDateFormat">
<constructor-arg type="java.lang.String" value="yyyy-MM-dd'T'HH:mm:ss.SSSZ"> </constructor-arg>
</bean>
</property>
</bean>
<bean id="jacksonProvider" class="com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider">
<property name="mapper" ref="jacksonMapper" />
</bean>
in jaxrs:server.....>
<jaxrs:providers>
<ref bean="jaxbProvider" />
<ref bean="jacksonProvider" />
</jaxrs:providers>
</jaxrs:server>
The application gets deployed without any issues, it gives good JSON while I give the request as "application/xml" and the response as "application/json".
When I try to give JSON in request by setting Content-Type=application/json I'm facing the 500 Internal Server Error
The request is getting logged in the log file thru CXF-logging.
The request is not at all landing in the service implementation class of my webservice.
The JSON in request body is :
{"SearchOrdersRequest":{"LoginCredentials":{"AppId":"BookStore","Username":"myuser","Password":"abcd1234","SecurityToken":"Vcvx45YilzX1"},"SearchHeader":{"SearchCategory":"Rep","FilterLogic":"1 AND 2","SearchParams":{"Field":"Order Number (s)","Operator":"EQUALS","Values":"600045335"}}}}
Any immediate help is appreciated.
In CXF documentation , you can see where you need to add json provider and include a dependency. But, I still getting errors when I tried to add jackson instead of jettison, after some hours I figured that you need to include one more jackson dependency.
Add JSON provider
<jaxrs:providers>
<bean class="org.codehaus.jackson.jaxrs.JacksonJaxbJsonProvider" />
</jaxrs:providers>
Add dependencies
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-xc</artifactId>
<version>1.9.12</version>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-jaxrs</artifactId>
<version>1.9.12</version>
</dependency>
As I undertood you, your application produces and consumes xml and json format. So, first of all. Make it sure that your cxf resource endpoint are able to do it.
#Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
#Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
Otherwise your request won't find any resource implementation. (at these line at class level or method level)
Then if this is not enough check out this jackson cxf integration:
<bean id="jsonProvider" class="com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider" />
<bean id="jsonContextResolver" class="net.sf.gazpachoquest.rest.support.JacksonContextResolver" />
Also
<jaxrs:server id="services" address="/">
<jaxrs:providers>
<ref bean="jsonProvider" />
<ref bean="jsonContextResolver" />
</jaxrs:providers>
</jaxrs:server>
The context resolver the class where the mapper is defined:
#Provider
public class JacksonContextResolver implements ContextResolver<ObjectMapper> {
private final ObjectMapper mapper = new ObjectMapper();
public JacksonContextResolver() {
/*
* Register JodaModule to handle Joda DateTime Objects.
* https://github.com/FasterXML/jackson-datatype-jsr310
*/
mapper.registerModule(new JSR310Module());
mapper.setSerializationInclusion(Include.NON_EMPTY);
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
}
#Override
public ObjectMapper getContext(Class<?> arg0) {
return mapper;
}
}
And just in case you deploy your application into a j2ee container, you may require a application config class:
#ApplicationPath("/api")
public class ApplicationConfig extends javax.ws.rs.core.Application{
#Override
public Set<Class<?>> getClasses() {
Set<Class<?>> classes = new HashSet<Class<?>>();
// add here your resources
classes.add(JacksonContextResolver.class);
classes.add(JacksonJsonProvider.class);
...
return classes;
}
Hope this help.
Some time ago I had problem with a json request when I had not jettison library in classpath.
Check http://www.javatips.net/blog/2012/02/cxf-restful-tutorial tutorial.
In order to use jackson library by overriding jettison, you need to configure jacksonprovider like following
<bean id="jacksonJsonProvider" class="com.student.CustomJsonProvider"/>
<jaxrs:providers>
<ref bean="jacksonJsonProvider" />
</jaxrs:providers>
In normal circumstances if you deploy to a EE server by simply adding that dependency the provider had to be automatically discovered and used (I tested width TomEE 7):
<dependency>
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-json-provider</artifactId>
<version>2.8.6 </version>
</dependency>
But be careful to use the correct version. CFX guide suggests a Jackson JAX-RS provider that is only compatible with Jackson 1 and not for Jackson 2. Look at that question for more details
This is happening because when you are using the jackson library with group com.fasterxml.jackson.jaxrs, the server is expecting the request in following format:
{
"LoginCredentials": {
"AppId" : "BookStore",
"Username":"myuser",
"Password":"abcd1234",
"SecurityToken":"Vcvx45YilzX1"
},
"SearchHeader":{
"SearchCategory":"Rep",
"FilterLogic":"1 AND 2",
"SearchParams":{
"Field":"Order Number (s)",
"Operator":"EQUALS",
"Values":"600045335"
}
}
}
But your client will be sending in following format:
{
"SearchOrdersRequest": {
"LoginCredentials": {
"AppId" : "BookStore",
"Username":"myuser",
"Password":"abcd1234",
"SecurityToken":"Vcvx45YilzX1"
},
"SearchHeader":{
"SearchCategory":"Rep",
"FilterLogic":"1 AND 2",
"SearchParams":{
"Field":"Order Number (s)",
"Operator":"EQUALS",
"Values":"600045335"
}
}
}
}
It seems to be the issue with this fasterxml version of library only. Using the library with group org.codehaus.jackson will fix the issue. Both request and response will be without root element.
So you need to replace the following maven dependency
<dependency>
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-json-provider</artifactId>
<version>2.9.3</version>
</dependency>
with
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-jaxrs</artifactId>
<version>1.9.13</version>
</dependency>
I checked that the library mentioned in the cxf docs (https://cxf.apache.org/docs/jax-rs-data-bindings.html#JAX-RSDataBindings-Jackson) is also the one with com.fasterxml.jackson.jaxrs but if you check the classes that are mentioned there in beans are from org.codehaus.jackson.jaxrs. So I think this is a mistake in docs and needs correction.

Spring #ResponseBody Jackson JsonSerializer with JodaTime

I have below Serializer for JodaTime handling:
public class JodaDateTimeJsonSerializer extends JsonSerializer<DateTime> {
private static final String dateFormat = ("MM/dd/yyyy");
#Override
public void serialize(DateTime date, JsonGenerator gen, SerializerProvider provider)
throws IOException, JsonProcessingException {
String formattedDate = DateTimeFormat.forPattern(dateFormat).print(date);
gen.writeString(formattedDate);
}
}
Then, on each model objects, I do this:
#JsonSerialize(using=JodaDateTimeJsonSerializer.class )
public DateTime getEffectiveDate() {
return effectiveDate;
}
With above settings, #ResponseBody and Jackson Mapper sure works. However, I don't like the idea where I keep writing #JsonSerialize. What I need is a solution without the #JsonSerialize on model objects. Is it possible to write this configuration somewhere in spring xml as a one configuration?
Appreciate your help.
Although you can put an annotation for each date field, is better to do a global configuration for your object mapper. If you use jackson you can configure your spring as follow:
<bean id="jacksonObjectMapper" class="com.company.CustomObjectMapper" />
<bean id="jacksonSerializationConfig" class="org.codehaus.jackson.map.SerializationConfig"
factory-bean="jacksonObjectMapper" factory-method="getSerializationConfig" >
</bean>
For CustomObjectMapper:
public class CustomObjectMapper extends ObjectMapper {
public CustomObjectMapper() {
super();
configure(Feature.WRITE_DATES_AS_TIMESTAMPS, false);
setDateFormat(new SimpleDateFormat("EEE MMM dd yyyy HH:mm:ss 'GMT'ZZZ (z)"));
}
}
Of course, SimpleDateFormat can use any format you need.
#Moesio pretty much got it. Here's my config:
<!-- Configures the #Controller programming model -->
<mvc:annotation-driven/>
<!-- Instantiation of the Default serializer in order to configure it -->
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapterConfigurer" init-method="init">
<property name="messageConverters">
<list>
<bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
<property name="objectMapper" ref="jacksonObjectMapper" />
</bean>
</list>
</property>
</bean>
<bean id="jacksonObjectMapper" class="My Custom ObjectMapper"/>
<bean id="jacksonSerializationConfig" class="org.codehaus.jackson.map.SerializationConfig"
factory-bean="jacksonObjectMapper" factory-method="getSerializationConfig" />
The bit that got me is that <mvc:annotation-driven/> makes its own AnnotationMethodHandler and ignores the one you make manually. I got the BeanPostProcessing idea from http://scottfrederick.blogspot.com/2011/03/customizing-spring-3-mvcannotation.html to configure the one that gets used, and voilĂ ! Works like a charm.
Same using JavaConfig of Spring 3:
#Configuration
#ComponentScan()
#EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter
{
#Override
public void configureMessageConverters(final List<HttpMessageConverter<?>> converters)
{
converters.add(0, jsonConverter());
}
#Bean
public MappingJacksonHttpMessageConverter jsonConverter()
{
final MappingJacksonHttpMessageConverter converter = new MappingJacksonHttpMessageConverter();
converter.setObjectMapper(new CustomObjectMapper());
return converter;
}
}
If you are using Spring Boot, try this in application.yml :
spring:
jackson:
date-format: yyyy-MM-dd
time-zone: Asia/Shanghai
joda-date-time-format: yyyy-MM-dd
If you simply have the Jackson JARs on your classpath, and return a #ResponseBody, Spring will automatically convert the Model object to JSON. You don't need to annotate anything in the Model to get this to work.

Jackson serializationConfig

I am using Jackson JSON in a Spring 3 MVC app. To not serialize each and every single Date field, I created a custom objectmapper that uses a specific DateFormat:
#Component("jacksonObjectMapper")
public class CustomObjectMapper extends ObjectMapper
{
Logger log = Logger.getLogger(CustomObjectMapper.class);
#PostConstruct
public void afterProps()
{
log.info("PostConstruct... RUNNING");
//ISO 8601
getSerializationConfig().setDateFormat(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SZ"));
}
//constructors...
}
This custom ObjectMapper is injected into the JsonConverter:
<bean id="jsonConverter" class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
<property name="supportedMediaTypes" value="application/json" />
<property name="objectMapper" ref="jacksonObjectMapper" /> <!-- defined in CustomObjectMapper -->
</bean>
There is no exception in the logs and serialization works, but it is not picking up the dateformat, it simple serializes to a timestamp. The #PostConstruct annotation works, the log statement in the method is in the logs.
Does anyone know why this fails?
You may also need to specify that you want textual Date serialization, by doing:
configure(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS, false);
(although I was assuming setting non-null date format might also trigger it, but maybe not)
Also, you can do configuration of mapper directly from constructor (which is safe). Not that it should change behavior, but would remove need for separate configuration method.
I've done the below which works to get around compatability with Java / PHP timestamps. Java uses milliseconds since EPOCH and PHP uses seconds so was simpler to use ISO dates.
I declare the below message adapters:
<bean id="messageAdapter"
class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="messageConverters">
<list>
<bean id="jacksonJsonMessageConvertor"
class="my.app.MyMappingJacksonHttpMessageConverter"/>
</list>
</property>
</bean>
And MyMappingJacksonHttpMessageConverter looks like the below:
public class MyMappingJacksonHttpMessageConverter extends MappingJacksonHttpMessageConverter {
public MyMappingJacksonHttpMessageConverter(){
super();
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(Feature.WRITE_DATES_AS_TIMESTAMPS, false);
setObjectMapper(objectMapper);
}
}
With the above all dates are written out in ISO format.
For Spring config application.properties
spring.jackson.serialization.fail-on-empty-beans=false