Spring Boot 1.2.5 & Jersey 2 ignore null fields - json

In Spring boot 1.2.5 with a Jersey 2 interface, how can I set the JSON marshaller to not include fields that have null values?
For example:
[
{
"created": 1433987509174,
"lastModified": 1433876475580,
"id": 1,
"example": "example1b"
},
{
"created": 1434502031212,
"lastModified": 1434502031212,
"id": 10000,
"example": "example1c"
},
{
"created": 1439151444176,
"lastModified": 1439151444176,
"id": 10011,
"example": null
}
]
The field "example": null should not be included in the json output at all, but here it is specifying it is null.
In my #SpringBootApplication class, I've tried adding:
#Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
final MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
final ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
converter.setObjectMapper(objectMapper);
return converter;
}
or
#Bean
#Primary
public Jackson2ObjectMapperBuilder objectMapperBuilder() {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.serializationInclusion(JsonInclude.Include.NON_NULL);
return builder;
}
or
#Primary
#Bean
public ObjectMapper mapper() {
final ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
return objectMapper;
}
and/or adding #JsonSerialize(include = Inclusion.NON_NULL) to the Object itself
But it still produces the same response above with the "example": null included.
It was working on Spring 3.0.7 with #JsonSerialize(include=Inclusion.NON_NULL) but that no longer works now that I've ported to Spring Boot 1.2.5.
I believe I've followed the documentation http://docs.spring.io/spring-boot/docs/current/reference/html/howto-spring-mvc.html#howto-customize-the-jackson-objectmapper and it's not working so I'm hoping someone might see something I'm missing? Thanks in advance!
Edit: Also just tried adding the class:
#Configuration
public class WebConfiguration extends WebMvcAutoConfiguration {
#Primary
#Bean
public ObjectMapper mapper() {
final ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
return objectMapper;
}
}
Solution:
package com.my.spring;
import javax.ws.rs.ext.ContextResolver;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.servlet.ServletProperties;
import org.springframework.context.annotation.Configuration;
import com.my.spring.service.rs.MyRestServiceImpl;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
#Configuration
public class JerseyConfiguration extends ResourceConfig {
public class ObjectMapperContextResolver implements ContextResolver<ObjectMapper> {
private final ObjectMapper mapper;
public ObjectMapperContextResolver() {
mapper = new ObjectMapper();
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
}
#Override
public ObjectMapper getContext(Class<?> type) {
return mapper;
}
}
public JerseyConfiguration() {
register(new ObjectMapperContextResolver());
register(MyRestServiceImpl.class); // My jax-rs implementation class
property(ServletProperties.FILTER_FORWARD_ON_404, true); // Not needed for this non_null issue
}
}

I don't know about mixing the Spring way (of configuring the mapper) and how Jersey handles this. But the Jersey way to configure the ObjectMapper is through a ContextResolver, as seen in this answer.
Then register the ObjectMapperContextResolver with your Jersey configuration.
public JerseyConfig extends ResourceConfig {
public JerseyConfig() {
...
register(ObjectMapperContextResolver.class);
}
}
Or if you are package scanning, the #Provider annotation will pick up the class.

Related

How to avoid null objects in my REST JSON response?

I'm getting something like this in my JSON response (I'm having a REST implementation in SpringBoot):
"estimatedDeliveryTimeWindow":{
"window":{}
}
I have set custom HTTPMessageCOnverters and configured objectMapper like this:
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
Also tried to remove default converters using below code:
#Bean
public HttpMessageConverters converters() {
MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
jsonConverter.setObjectMapper(objectMapper);
return new HttpMessageConverters(false, Arrays.asList(jsonConverter));
}
Nothing seems to work. I still see null objects within objects. These objects are complex objects nested with primitive types and custom objects. What else I can try?
Please add #JsonInclude(Include.NON_NULL) before the class files
#JsonInclude(Include.NON_NULL)
public class MobileLoginVO {
private String otpDetailsId;
public String getOtpDetailsId() {
return otpDetailsId;
}
public void setOtpDetailsId(String otpDetailsId) {
this.otpDetailsId = otpDetailsId;
}
}
You need to inform somehow to spring to use your message converter.
This should do the work:
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.List;
#Configuration
#EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
public MappingJackson2HttpMessageConverter messageConverter() {
MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
jsonConverter.setObjectMapper(objectMapper);
return jsonConverter;
}
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(messageConverter());
}
}

Spring rest template cannot convert from text/html

Spring rest template throws me the following exeption
org.springframework.web.client.RestClientException: Could not extract response: no suitable HttpMessageConverter found for response type [class [Lcom.flightsms.core.dto.AirlineResponseDTO;] and content type [text/html;charset=UTF-8]
Here my json response
[
{
"airlineId": "1",
"nameAirline": "American Airlines",
"codeIataAirline": "AA",
"iataPrefixAccounting": "1",
"codeIcaoAirline": "AAL",
"callsign": "AMERICAN",
"type": "scheduled",
"statusAirline": "active",
"sizeAirline": "963",
"ageFleet": "10.9",
"founding": "1934",
"codeHub": "DFW",
"nameCountry": "United States",
"codeIso2Country": "US"
}
]
dto class
#Data
public class AirlineResponseDTO {
private String airlineId;
private String nameAirline;
private String codeIataAirline;
private String iataPrefixAccounting;
private String codeIcaoAirline;
private String callsign;
private String type;
private String statusAirline;
private String sizeAirline;
private String ageFleet;
private String founding;
private String codeHub;
private String nameCountry;
private String codeIso2Country;
}
I suspect that the matter is in the converter. I changed the converter configuration but this did not work
#Configuration
public class MvcConfigSupport extends WebMvcConfigurationSupport {
protected void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(converter());
addDefaultHttpMessageConverters(converters);
}
#Bean
MappingJackson2HttpMessageConverter converter() {
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setSupportedMediaTypes(Arrays.asList(MediaType.TEXT_HTML));
return converter;
}
}
Problem solved.
#Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setSupportedMediaTypes(Collections.singletonList(MediaType.TEXT_HTML));
restTemplate.getMessageConverters().add(converter);
return restTemplate;
}
Try something like that the default converters are not registered by default and an end user has to be explicit about registering the defaults :
#Configuration
public class WebConfig extends WebMvcConfigurationSupport {
#Bean
public MappingJackson2HttpMessageConverter customJackson2HttpMessageConverter() {
MappingJackson2HttpMessageConverter jsonConverter =
new MappingJackson2HttpMessageConverter();
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(
DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,false);
jsonConverter.setObjectMapper(objectMapper);
return jsonConverter;
}
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>>
converters) {
converters.add(customJackson2HttpMessageConverter());
super.addDefaultHttpMessageConverters();
}
}
Moreover please try fix type exception. Content type [text/html;charset=UTF-8] received from the service, the real content type should be application/json;charset=UTF-8
To get all ContentType you can do your custom converter like that:
List<HttpMessageConverter<?>> messageConverters =
new ArrayList<HttpMessageConverter<?>>();
MappingJackson2HttpMessageConverter converter =
new MappingJackson2HttpMessageConverter();
converter.setSupportedMediaTypes(Arrays.asList({MediaType.ALL}));
messageConverters.add(converter);
restTemplate.setMessageConverters(messageConverters);
Use RestTemplateBuilders to create the resttemplate clients. The builders will add the default Message converters.

Jackson2JsonMessageConverter serializing LocalDateTime in specific pattern

I am trying to send an event using RabbitMQ and SpringBoot.
#Configuration class:
#Bean
public Jackson2JsonMessageConverter producerJackson2MessageConverter() {
return new Jackson2JsonMessageConverter();
}
Event class:
public class TheEvent{
#JsonFormat(pattern = "dd::MM::yyyy")
private LocalDateTime date;
//setters getters
}
When I send it, it arrives as:
{"month":"JULY","year":2018,"dayOfMonth":12,"dayOfWeek":"THURSDAY","dayOfYear":193,"hour":16,"minute":29,"nano":835000000,"second":24,"monthValue":7,"chronology":{"id":"ISO","calendarType":"iso8601"}},"direction":1}"
How can I serialize this date object in predefined pattern? (Remember that I just registering bean Jackson2JsonMessageConverter)
Also tried this:
#Bean
#Primary
public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) {
ObjectMapper objectMapper = builder.createXmlMapper(false).build();
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, true);
return objectMapper;
}
It works for me:
#Bean
public Jackson2JsonMessageConverter converter(Jackson2ObjectMapperBuilder builder) {
ObjectMapper objectMapper = builder.createXmlMapper(false).build();
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, true);
return new Jackson2JsonMessageConverter(objectMapper);
}
In project I used RabbitListener and that Jackson2JsonMessageConverter
Listener:
#RabbitListener(queues = "${spring.rabbitmq.queue}")
#Transactional
public void receiveSocialPost(SocialPost socialPost) {
}

Auto include CSV header with Spring MVC and jackson-dataformat-csv

I'm struggling with jackson-dataformat-csv to make it include CSV headers.
Right now I am able to output collections of entities (List) but the headers are not there, which make the file impossible to parse because columns doesn't have a title.
My MVC config (shortened) :
#Configuration
#EnableWebMvc
public class MvcConfig extends WebMvcConfigurerAdapter {
#Autowired
private Environment env;
public static final String JSON_OBJECT_MAPPER_NAME = "json";
public static final String CSV_OBJECT_MAPPER_NAME = "csv";
private static final TimeZone OUTPUT_DATE_TIMEZONE = TimeZone.getTimeZone(ZoneOffset.UTC);
private static final DateFormat OUTPUT_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
#Autowired
#Qualifier(value = JSON_OBJECT_MAPPER_NAME)
private ObjectMapper jsonObjectMapper;
#Autowired
#Qualifier(value = CSV_OBJECT_MAPPER_NAME)
private ObjectMapper csvObjectMapper;
public MvcConfig() {
super();
}
#Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(true);
configurer.ignoreAcceptHeader(false);
configurer.defaultContentType(MediaType.APPLICATION_JSON);
configurer.useJaf(false);
final Map<String,MediaType> mediaTypes = new HashMap<>();
mediaTypes.put("html", MediaType.TEXT_HTML);
mediaTypes.put("json", MediaType.APPLICATION_JSON);
mediaTypes.put("csv", new MediaType("text","csv", Charset.forName("utf-8")));
configurer.mediaTypes(mediaTypes);
}
#Bean(name = JSON_OBJECT_MAPPER_NAME)
#Primary
public ObjectMapper jsonObjectMapper(){
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.indentOutput(Boolean.parseBoolean(env.getProperty("jsonPrettyPrint")));
builder.timeZone(OUTPUT_DATE_TIMEZONE);
builder.dateFormat(OUTPUT_DATE_FORMAT);
return builder.build();
}
#Bean(name = CSV_OBJECT_MAPPER_NAME)
public ObjectMapper csvObjectMapper(){
CsvMapper csvMapper = new CsvMapper();
//csvMapper.enable(CsvParser.Feature.WRAP_AS_ARRAY);
csvMapper.setTimeZone(OUTPUT_DATE_TIMEZONE);
csvMapper.setDateFormat(OUTPUT_DATE_FORMAT);
csvMapper.registerModule(new CsvMappingModule());
csvMapper.registerModule(new JodaModule());
return csvMapper;
}
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(createJsonHttpMessageConverter());
converters.add(createCsvHttpMessageConverter());
}
private HttpMessageConverter<Object> createJsonHttpMessageConverter() {
return createHttpMessageConverter(jsonObjectMapper, MediaType.APPLICATION_JSON, MediaType.APPLICATION_JSON_UTF8);
}
private HttpMessageConverter<Object> createCsvHttpMessageConverter() {
return createHttpMessageConverter(csvObjectMapper, TEXT_CSV);
}
private HttpMessageConverter<Object> createHttpMessageConverter(ObjectMapper objectMapper, MediaType... supportedMediaTypes){
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(objectMapper);
converter.setSupportedMediaTypes(Lists.newArrayList(supportedMediaTypes));
return converter;
}
}
A controller that ouput a list of values :
#Controller
#RequestMapping("/api/history")
public class HistoricController {
#Autowired
public IHistoryService historyService;
#Autowired
public IThingService thingService;
#RequestMapping(value = "/{id}", method = RequestMethod.GET)
#ResponseBody
public List<HistoryDTO> findHistory(#PathVariable("id") Long thingId){
Thing thing = thingService.find(thingId);
return historyService.findByThing(thing);
}
}
I am able to return in JSON format:
[
{
"location": {
"id": 101483,
"name": "City A"
},
"dateEnteredLocation": "2016-06-06T18:44:03.000Z",
"dateLeavingLocation": "2016-06-13T13:02:34.000Z"
},
{
"location": {
"id": 101483,
"name": "City A"
},
"dateEnteredLocation": "2016-06-13T16:02:34.000Z",
"dateLeavingLocation": "2016-06-15T11:54:57.000Z"
},
{
"location": {
"id": 101485,
"name": "City C"
},
"dateEnteredLocation": "2016-06-16T04:05:06.000Z",
"dateLeavingLocation": "2016-06-16T11:34:58.000Z"
}
]
But when I try to use the CSV format I obtain :
2016-06-06T18:44:03.000Z,2016-06-13T13:02:34.000Z,101483,City A
2016-06-13T16:02:34.000Z,2016-06-15T11:54:57.000ZZ,101483,City A
2016-06-16T04:05:06.000Z,2016-06-16T11:34:58.000Z,101485,City C
So the format in CSV, it's fine. But there are no headers included.
I need headers to make the file understandable by humans or machines.
How can I make jackson csv mapper to automatically include headers. The headers names should be the same as the ones used for Json (which uses Entity #Json... annotations) ?
I want to keep it as generic as possible. I don't want to have to writer specific MVC controllers for CSV.
The mechanism Jackson uses is that CsvSchema used needs to have header enabled, so something like:
CsvMapper mapper = new CsvMapper();
CsvSchema schema = CsvSchema.emptySchema().withHeader();
String csv = mapper.writer(schema).writeValueAsString(...);
the challenge then is that of how to pass CsvSchema to use; or possible CsvWriter configured to use one. I am not sure if Spring MVC has explicit support for doing that.

Spring boot Jersey Jackson

I have a question related to the Jackson configuration on my Spring boot project
As described on spring boot blog
I try to customize my Object serialization.
After added a new config bean in my config
#Bean
public Jackson2ObjectMapperBuilder jacksonBuilder() {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.propertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);
return builder;
}
When I try to output an instance of my class User the json result is not in CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES
Class User {
private String firstName = "Joe Blow";
public String getFirstName() {
return firstName;
}
}
json output is :
{
"firstName": "Joe Blow"
}
and not
{
"first_name": "Joe Blow"
}
Maybe I need to register something in my Jersey config to activate my custom obejctMapper Config
#Configuration
public class JerseyConfig extends ResourceConfig {
public JerseyConfig() {
packages("my.package);
}
}
Thanks
The general way to configure the ObjectMapper for JAX-RS/Jersey applications is use a ContextResolver. For example
#Provider
public class ObjectMapperContextResolver implements ContextResolver<ObjectMapper> {
private final ObjectMapper mapper;
public ObjectMapperContextResolver() {
mapper = new ObjectMapper();
mapper.setPropertyNamingStrategy(
PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES
);
}
#Override
public ObjectMapper getContext(Class<?> type) {
return mapper;
}
}
It should be picked up with the package scan, or you can explicitly register it, if it's not within the package scope
public JerseyConfig() {
register(new ObjectMapperContextResolver());
// Or if there's is an injection required
// register it as a .class instead of instance
}
The ContextResolver is called during the marshalling and unmarshalling. The class/type being serialzed or deserialized into will be passed to the getContext method. So you could even use more than one mapper for different types, or even more use cases.
UPDATE
Starting from Spring Boot 1.4, you can just create an ObjectMapper Spring bean, and Spring Boot will create the ContextResolver for you, and use your ObjectMapper
// in your `#Configuration` file.
#Bean
public ObjectMapper mapper() {}