Override JSON properties in #RestController in Spring 4.3.3 WebMVC - spring

I want to override the following properties in Spring RestController in WebMVC.
objectMapper.setSerializationInclusion(Include.NON_NULL);
objectMapper.configure(SerializationFeature.WRAP_ROOT_VALUE, true);
objectMapper.setDateFormat(DATEFORMAT);
I've tried the following in multiple combinations, but nothing worked. Still getting the null value without root name in the response.
#EnableWebMvc
public class ApplicationContextConfig extends WebMvcConfigurerAdapter {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
//MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.setSerializationInclusion(Include.NON_NULL);
objectMapper.configure(SerializationFeature.WRAP_ROOT_VALUE, true);
objectMapper.setDateFormat(DATEFORMAT);
//jsonConverter.setObjectMapper(objectMapper);
MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter(objectMapper);
//jsonConverter.setJsonPrefix(jsonPrefix);
converters.add(jsonConverter);
}
#Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
for (HttpMessageConverter<?> converter : converters) {
if (converter instanceof MappingJackson2HttpMessageConverter) {
MappingJackson2HttpMessageConverter jsonMessageConverter = (MappingJackson2HttpMessageConverter) converter;
ObjectMapper objectMapper = jsonMessageConverter.getObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.setSerializationInclusion(Include.NON_NULL);
objectMapper.configure(SerializationFeature.WRAP_ROOT_VALUE, true);
objectMapper.setDateFormat(DATEFORMAT);
break;
}
}
}
#Bean
#Primary
public ObjectMapper getObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setSerializationInclusion(Include.NON_NULL);
objectMapper.configure(SerializationFeature.WRAP_ROOT_VALUE, true);
objectMapper.setDateFormat(DATEFORMAT);
return objectMapper;
}
}
Reponse:
{
"Message": null,
"Admin": null,
"Company": null
}

You should "substitute" the object mapper bean provided by spring with your own and configure it.
#Configuration
public class MyJacksonCustomConfiguration {
#Bean
public ObjectMapper objectMapper() { // note the name should be 'objectMapper'
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setSerializationInclusion(Include.NON_NULL);
objectMapper.configure(SerializationFeature.WRAP_ROOT_VALUE, true);
objectMapper.setDateFormat(DATEFORMAT);
return objectMapper;
}
}
All in all, make sure that only one bean of type object mapper should exist in the application context. Apart of fact that its a pretty "heavy" object, if spring will inject a wrong mapper you won't be able to benefit from customization that you've done.

Related

Using ContextResolver to register ObjectMapper in runtime

We're using JAX-RS (Jersey implementation) to call to external systems.
On JAX-RS Client's creation I'm registering the below context resolver to use custom ObjectMapper:
public class JacksonObjectMapperProvider implements ContextResolver<ObjectMapper>
{
#Override
public ObjectMapper getContext(Class<?> type)
{
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true);
mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
mapper.configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, false);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.configure(SerializationFeature.WRITE_NULL_MAP_VALUES, false);
mapper.configure(SerializationFeature.WRITE_EMPTY_JSON_ARRAYS, true);
mapper.setSerializationInclusion(Include.NON_NULL);
return mapper;
}
}
But I don't want to have the ObjectMapper defined in JacksonObjectMapperProvider. I want JacksonObjectMapperProvider to be able to retrieve it in runtime from somewhere, or have someone set the ObjectMapper on JacksonObjectMapperProvider.
I cannot do something like bellow, because the ObjectMapper is defined on some instance that creating the jax-rs Client. And here I don't have a reference to that instance:
public class JacksonObjectMapperProvider implements ContextResolver<ObjectMapper>
{
#Override
public ObjectMapper getContext(Class<?> type)
{
return someService.getObjectMapper();
}
}
Is there another way to do it?
Is there a way to pass data to JacksonObjectMapperProvider when registering on Client?
The solution is easier then I thought, instead of registering the class:
ClientConfig clConfig = new ClientConfig();
client.register(JacksonObjectMapperProvider.class);
as I did, you can register an instance of the class, and on instance creation pass whatever you want:
ClientConfig clConfig = new ClientConfig();
client.register(new JacksonObjectMapperProvider(objectMapper));
The updated provider:
public class JacksonObjectMapperProvider implements ContextResolver<ObjectMapper>
{
private ObjectMapper mapper;
public JacksonObjectMapperProvider(ObjectMapper mapper)
{
this.mapper = mapper;
}
#Override
public ObjectMapper getContext(Class<?> type)
{
return mapper;
}
}

JmsMessagingTemplate is message converter broken?

I was trying to use a JmsMessagingTemplate with a JmsTemplate that has a org.springframework.jms.support.converter.MappingJackson2MessageConverter but the problem is that navigating up on the JmsMessagingTemplate to org.springframework.messaging.core.AbstractMessageSendingTemplate where the converter used is an implementation of org.springframework.messaging.converter.MessageConverter which doesn't seem compatible with org.springframework.jms.support.converter.MappingJackson2MessageConverter.
Is this broken or I'm trying to do something wrong here?
You haven't added your configuration code. So assuming that you have not added bean for message converter. Please find below code snippet for the configuration, hope that will resolve the problem.
#Bean
public MessageConverter jacksonJmsMessageConverter() {
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
converter.setTargetType(MessageType.TEXT);
converter.setTypeIdPropertyName("_type");
converter.setObjectMapper(objectMapper());
return converter;
}
#Bean
public ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
return objectMapper;
}
#Bean
public JmsTemplate jmsTemplate(ConnectionFactory connectionFactory,
DefaultJmsListenerContainerFactoryConfigurer configurer) {
JmsTemplate jmsTemplate = new JmsTemplate();
jmsTemplate.setConnectionFactory(connectionFactory());
jmsTemplate.setMessageConverter(jacksonJmsMessageConverter());
return jmsTemplate;
}
#Bean
public ConnectionFactory connectionFactory() {
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory();
connectionFactory.setBrokerURL(url);
connectionFactory.setUserName(user);
connectionFactory.setPassword(password);
return connectionFactory;
}
Since I'm wrapping around a jmsMessagingTemplate I had to set the converter explicitly like this:
public DelegatingJmsMessagingTemplate(JmsTemplate jmsTemplate) {
this.jmsMessagingTemplate = new JmsMessagingTemplate(jmsTemplate);
final var messagingMessageConverter = new MessagingMessageConverter(jmsTemplate.getMessageConverter());
this.jmsMessagingTemplate.setJmsMessageConverter(messagingMessageConverter); //seems to do the trick
this.jmsMessagingTemplate.setDefaultDestinationName(jmsTemplate.getDefaultDestinationName());
}
now both publisher and subscriber convert the message accordingly.
As I mention in a comment, I found out that org.springframework.jms.core.JmsMessagingTemplate have a org.springframework.jms.core.JmsMessagingTemplate.MessagingMessageCreator where the real conversion happens.

Override fasterxml jackson ObjectMapper used by Spring Integration dsl

Using Transformers.toJson() my json date looks like this:
"createdDate":{"year":2017,"month":"OCTOBER","monthValue":10,"dayOfMonth":25,"hour":7,"minute":57,"second":36,"nano":972000000,"dayOfWeek":"WEDNESDAY","dayOfYear":298,"chronology":{"calendarType":"iso8601","id":"ISO"}}
Here is the outbound ampq configuration:
#Bean
public IntegrationFlow outboundCdrRabbitFlowDefinition() {
return IntegrationFlows.from(CHANNEL_NAME)
.transform(Transformers.toJson())
.handle(Amqp.outboundAdapter(new RabbitTemplate(cachingConnectionFactory))
.routingKey("routing-key"))
.get();
}
The consumer of the rabbit queue expects the format "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
Is there any way i can override the default ObjectMapper used by spring integration?
For example i have the this configuration in my web api config that extends WebMvcConfigurerAdapter:
#Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.setTimeZone(UTC_TIME_ZONE);
mapper.setDateFormat(ISO_8601_DATE_FORMAT);
mapper.registerModule(new Jdk8Module());
mapper.registerModule(new JavaTimeModule());
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return mapper;
}
#Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
mappingJackson2HttpMessageConverter.setObjectMapper(objectMapper());
return mappingJackson2HttpMessageConverter;
}
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(mappingJackson2HttpMessageConverter());
super.configureMessageConverters(converters);
}
Is there some way to reuse the objectMapper bean in Spring Integration so the mapping configuration is the same across all my outbound endpoints, web api http or integration ampq?
There is an overloaded version of the Transformers.toJson():
Transformers.toJson(new Jackson2JsonObjectMapper(objectMapper))

Spring Boot integration test - TestRestTemplate how to set response Content-Type to UTF-8

I tried to integrate test Spring Boot application, now I got one issue
org.springframework.web.client.RestClientException: Could not extract response: no suitable HttpMessageConverter found for response type [class org.springframework.http.ResponseEntity] and content type [application/x-json;charset=iso-8859-1]
at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:109)
at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:917)
at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:901)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:655)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:613)
at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:531)
at org.springframework.boot.test.web.client.TestRestTemplate.exchange(TestRestTemplate.java:735)
If I do below
#Before
public void init() {
List<HttpMessageConverter<?>> converters = restTemplate.getRestTemplate().getMessageConverters();
for (HttpMessageConverter converter : converters) {
if (converter instanceof MappingJackson2HttpMessageConverter) {
MappingJackson2HttpMessageConverter jsonConverter = (MappingJackson2HttpMessageConverter) converter;
jsonConverter.setObjectMapper(new ObjectMapper());
jsonConverter.setSupportedMediaTypes(ImmutableList
.of(new MediaType("application", "x-json", Charset.forName("iso-8859-1"))));
}
}
}
I added the application/x-json and iso8859-1 support to MappingJackson2HttpMessageConverter, it can works well.
This is an workaround, but I want to know why the response content-type is application/x-json;charset=iso-8859-1? I have the produces configuration in my controller
#RequestMapping(method = RequestMethod.GET, value = "/{id}", produces = "application/json;charset=UTF-8")
Does someone know how to config it and let the response use "application/json;charset=UTF-8" content type?
Below is my test method
#Autowired
protected TestRestTemplate restTemplate;
#Test
public void testGetPerformanceObligationById() {
PerformanceObligationEntity entity = restTemplate.getForObject("/performance-obligations/{id}", PerformanceObligationEntity.class, "InvalidId");
Assert.assertNull(entity.getId());
}
I added one filter
#TestConfiguration
static class Config {
#Bean
public CharacterEncodingFilter characterEncodingFilter() {
final CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
characterEncodingFilter.setEncoding("UTF-8");
characterEncodingFilter.setForceEncoding(true);
return characterEncodingFilter;
}
}
Also modify the converter to
#Before
public void init() {
List<HttpMessageConverter<?>> converters =
restTemplate.getRestTemplate().getMessageConverters();
for (HttpMessageConverter converter : converters) {
if (converter instanceof MappingJackson2HttpMessageConverter) {
MappingJackson2HttpMessageConverter jsonConverter = (MappingJackson2HttpMessageConverter) converter;
jsonConverter.setObjectMapper(new ObjectMapper());
jsonConverter.setSupportedMediaTypes(ImmutableList
.of(new MediaType("application", "x-json", Charset.forName("UTF-8"))));
}
}
}
Now I got application/x-json;charset=utf-8, so just remain how to config from application/x-json to application/json

Resttemplate - how to post object with HAL representation?

When attempting to post to a Spring-Data-Rest web service via RestTemplate, the JSON representation of my domain object is being converted to a full blown JSON object that isn't in HAL representation. My assumption here is that I need to register the Jackson2HalModule as a deserializer though am not sure how to do that considering I register it to the objectMapper. The serialization works correctly when calling GET on the webservice, just not for POST/PUT:
Request outputBuffer field:
{
"id" : 1,
"name" : "Name",
"description" : "",
"childObject" : {
"id" : 1,
"name" : "test"
}
}
Rest Template configuration:
#Bean
public ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JodaModule());
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
objectMapper.setDateFormat(new ISO8601DateFormat());
objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
objectMapper.registerModule(new Jackson2HalModule());
return objectMapper;
}
public void configureMessageConverters(
List<HttpMessageConverter<?>> messageConverters) {
MappingJackson2HttpMessageConverter jsonMessageConverter = new MappingJackson2HttpMessageConverter();
jsonMessageConverter.setObjectMapper(objectMapper());
jsonMessageConverter.setSupportedMediaTypes(MediaType
.parseMediaTypes("application/hal+json,application/json"));
messageConverters.add(jsonMessageConverter);
}
#Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
configureMessageConverters(messageConverters);
restTemplate.setMessageConverters(messageConverters);
return restTemplate;
}
Request Headers:
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
Calling method:
ResponseEntity<DomainObject> responseEntity =
restTemplate.exchange(this.getBaseUri() + resolveResource(), HttpMethod.POST, new HttpEntity(domainObject,createHttpHeaders(tenantId)), DomainObject.class);
I think you should not register your own ObjectMapper. Spring creates it for you and also registers all the modules needed. So I would just try to remove your ObjectMapper bean.
If you need to customize the ObjectMapper you could use a Jackson2ObjectMapperBuilder. See the documentation for more details - http://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#howto-customize-the-jackson-objectmapper
#Bean
public Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder() {
return Jackson2ObjectMapperBuilder //
.json() //
.locale(ENGLISH) //
.timeZone("UTC") //
.indentOutput(true) //
.serializationInclusion(NON_NULL) //
.featuresToDisable(WRITE_DATES_AS_TIMESTAMPS, FAIL_ON_UNKNOWN_PROPERTIES) //
;
}
I would also let spring take care of the message converters:
So let spring inject them when you create the RestTemplate - so something like this:
#Bean
public RestTemplate restTemplate(List<HttpMessageConverter<?>> messageConverters) {
RestTemplate restTemplate = new RestTemplate();
restTemplate.setMessageConverters(messageConverters);
return restTemplate;
}

Resources