Spring-Boot: Jackson serialization configuration not respected - java

I am working with spring-boot 2.5.4 and trying to set default-property-inclusion=non_null globally, so that null values no longer show up in my spring web server's responses.
I have configured spring.jackson.default-property-inclusion=non_null in my project's application.yml as described in an earlier question.
spring:
jackson:
default-property-inclusion: non_null
Unfortunately, null-values are still included in my controller's output:
{
"enabled": true,
"name": "foo",
"subdomain": null,
"tenantId": null
}
What works however is if I add non-null directly to my dto:
#Data
#NoArgsConstructor
#JsonInclude(Include.NON_NULL)
public class GlobalTenantGet {
private UUID tenantId;
private String name;
private String subdomain;
private boolean enabled;
}
yielding
{
"enabled": true,
"name": "foo"
}
as expected.
question
Why does configuring #JsonInclude locally on the dto cause the null properties to disappear as expected but why does configuring spring.jackson.default-property-inclusion=non_null not have the same effect?
additional info
1. Debugging
Setting a breakpoint in org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration.JacksonObjectMapperConfiguration#jacksonObjectMapper shows, that the properties configured in application.yaml really made it to jackson:
2. Autowiring ObjectMapper in the controller
I get the expected behavior when autowiring the ObjectMapper in my controller and serializing my object directly via objectMapper.writeValueAsString().
other ways to configure the ObjectMapper
These are mostly in reply to Pawel Woroniecki's answer
1. Using #primary
I have a
#Configuration
public class ObjectMapperConfiguration {
#Primary
#Bean
public ObjectMapper objectMapper() {
final var om = new ObjectMapper();
om.setSerializationInclusion(Include.NON_NULL);
return om;
}
}
still includes null properties in the output
2. Jackson2ObjectMapperBuilder with serializationInclusion
#Configuration
public class ObjectMapperConfiguration {
#Bean
public Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder() {
return new Jackson2ObjectMapperBuilder().serializationInclusion(JsonInclude.Include.NON_NULL);
}
}
still yields
{
"enabled": true,
"name": "foo",
"subdomain": null,
"tenantId": null
}
3. define MappingJackson2HttpMessageConverter
#Configuration
public class ObjectMapperConfiguration {
#Bean
public MappingJackson2HttpMessageConverter messageMappingConverter() {
final var om = new ObjectMapper();
om.setSerializationInclusion(Include.NON_NULL);
return new MappingJackson2HttpMessageConverter(om);
}
}
still no bueno :-(

It seems #EnableWebMvc is the culprit. I have a WebMvcConfigurer that looks like this:
#Configuration
#EnableWebMvc // <----- this is the culprit!
public class WebConfiguration implements WebMvcConfigurer {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedMethods("OPTIONS", "GET", "POST", "PUT", "PATCH", "DELETE")
.allowedOrigins("example.com")
.allowedHeaders("*");
}
#Bean
public KeycloakSpringBootConfigResolver KeycloakConfigResolver() {
return new KeycloakSpringBootConfigResolver();
}
#Bean
public InternalResourceViewResolver defaultViewResolver() {
return new InternalResourceViewResolver();
}
}
commenting out #EnableWebMvc allows everything to work as expected.

There are several possible approaches to solve this issue and some of them are clearly explained here: https://www.baeldung.com/spring-boot-customize-jackson-objectmapper
Generally you can:
Override default ObjectMapper by creating custom ObjectMapper bean and marking it as #Primary (the simplest way but heavily impacts your application as all ObjectMapper instances you inject in code will use this one by default which may be good or not for you)
Define custom bean for Jackson2ObjectMapperBuilder and set proper serializationInclusion on this builder.
Register custom HTTP message converter in Spring which will use custom ObjectMapper by defining bean for MappingJackson2HttpMessageConverter (constructor of this converter accepts ObjectMapper so you can pass properly configured mapper there)

Related

Use Jackson Objectmapper configured by Spring boot in Hibernate

I want to configure Hibernate to use Jackson's Objectmapper created by Spring to map between json and entities. In the project I'm working on I already configured Jooq to use the Spring's ObjectMapper but I'm having trouble how to configure Hibernate to use it. The goal in the end is that both Jooq and Hibernate would use the same ObjectMapper.
I checked this article by Vlad. Unfortunately all the tips given in the article don't work for the project I'm working on.
Here's an example configuration I tried
#Configuration
public class HibernateConfiguration implements HibernatePropertiesCustomizer {
//Autowire Objectmapper created by Spring
#Autowired
ObjectMapper objectMapper;
#Override
public void customize(Map<String, Object> hibernateProperties) {
ObjectMapperSupplier objectMapperSupplier = () -> objectMapper;
// Below config doesn't work since Hibernate types creates it's own mapper
hibernateProperties.put("hibernate.types.jackson.object.mapper", objectMapperSupplier);
}
Also tried the same approach by adding the Objectmapper to hibernate-types.properties.
#Used by Hibernate but cannot get reference of Spring managed ObjectMapper since this is class is called outside of Spring's context.
hibernate.types.jackson.object.mapper=path.to.ObjectMapperSupplier
Another approach I used but it fails with a NullpointerException when converting from JSON to an entity in JsonTypeDescriptor class.
#Configuration
public class HibernateConfiguration implements HibernatePropertiesCustomizer{
#Autowired
ObjectMapper objectMapper;
#Override
public void customize(Map<String, Object> hibernateProperties) {
// Underlying implementation needs some JavaType or propertyClass, otherwise when converting
// from JSON we get a nullpointer.
var jsonBinaryType = new JsonBinaryType(objectMapper);
hibernateProperties.put("hibernate.type_contributors", (TypeContributorList) () ->
Collections.singletonList((typeContributions, serviceRegistry) ->
typeContributions.contributeType(jsonBinaryType)));
}
Below is the type declaration for entity super class.
// This makes Hibernate types create it's own mapper.
#TypeDef(name = "jsonb", typeClass = JsonBinaryType.class)
#MappedSuperclass
public abstract class Entity{
}
So, are there any possible solutions how I can hook up Spring managed ObjectMapper to Hibernate?
I finally figured this out, but it is kind of an creative solution...
TLDR: I have a bean that stores the Spring-configured objectMapper in a static field. A BeanFactoryPostProcessor ensures that this bean is initialized before Hibernate (types) tries to load / get the ObjectMapper.
hibernate.properties
hibernate.types.jackson.object.mapper=com.github.lion7.example.HibernateObjectMapperSupplier
HibernateObjectMapperSupplier.kt
package com.github.lion7.example
import com.fasterxml.jackson.databind.ObjectMapper
import com.vladmihalcea.hibernate.type.util.ObjectMapperSupplier
import org.springframework.beans.factory.config.BeanFactoryPostProcessor
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory
import org.springframework.stereotype.Component
class HibernateObjectMapperSupplier : ObjectMapperSupplier {
override fun get(): ObjectMapper =
ObjectMapperHolder.objectMapper
}
#Component
class ObjectMapperHolder(objectMapper: ObjectMapper) {
companion object {
lateinit var objectMapper: ObjectMapper
}
init {
Companion.objectMapper = objectMapper
}
}
#Component
class ObjectMapperDependencyFixer : BeanFactoryPostProcessor {
override fun postProcessBeanFactory(beanFactory: ConfigurableListableBeanFactory) {
val beanDefinition = beanFactory.getBeanDefinition("entityManagerFactory")
val oldDependsOn = beanDefinition.dependsOn ?: emptyArray()
val newDependsOn = oldDependsOn + "objectMapperHolder"
beanDefinition.setDependsOn(*newDependsOn)
}
}
Same code as gist: https://gist.github.com/lion7/c8006b69a309e38183deb69124b888b5
A Java implementation.
#Component
public class HibernateObjectMapper implements Supplier<ObjectMapper> {
private static ObjectMapper objectMapper;
#Autowired
public void setObjectMapper(ObjectMapper objectMapper) {
HibernateObjectMapper.objectMapper = objectMapper;
}
#Override
public ObjectMapper get() {
return objectMapper;
}
}
If you define your own JPA beans, simply add #DependsOn("hibernateObjectMapper") to their config. Otherwise you need a BeanPostProcessor to add the dependency to the autoconfigured bean:
#Component
class HibernateBeanDependencyProcessor implements BeanFactoryPostProcessor {
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory factory) {
BeanDefinition beanDefinition = factory.getBeanDefinition("entityManagerFactory");
String[] dependsOn = beanDefinition.getDependsOn();
dependsOn = dependsOn == null ? new String[]{} : dependsOn;
String[] newDependsOn = new String[dependsOn.length + 1];
System.arraycopy(dependsOn, 0, newDependsOn, 1, dependsOn.length);
newDependsOn[0] = "hibernateObjectMapper";
beanDefinition.setDependsOn(newDependsOn);
}
}
As for the property, hibernate.types.* don't work when set programmatically. The library looks directly in the hibernate.properties, hibernate-types.properties, and application.properties files.
I think that I've found solution to do it programmatically (without magic with fixing dependency graph).
HibernateConfiguration.kt
#Configuration(proxyBeanMethods = false)
class HibernateConfiguration {
#Bean
fun hibernatePropertiesCustomizer(
objectMapper: ObjectMapper // Thanks to that Spring can create correct dependency graph
): HibernatePropertiesCustomizer =
HibernatePropertiesCustomizer { hibernateProperties ->
HibernateObjectMapperSupplier.objectMapper = objectMapper
hibernateProperties["hibernate.types.jackson.object.mapper"] = HibernateObjectMapperSupplier::class.qualifiedName
}
}
HibernateObjectMapperSupplier.kt
class HibernateObjectMapperSupplier : Supplier<ObjectMapper> {
override fun get(): ObjectMapper {
return objectMapper
}
companion object {
lateinit var objectMapper: ObjectMapper
}
}
System.getProperties().put(
Configuration.PropertyKey.JACKSON_OBJECT_MAPPER.getKey(),
MyObjectMapperSupplier.class.getName()
);

Null values returned using WebMvcConfigurationSupport instead of WebMvcConfigurerAdapter

As WebMvcConfigurerAdapter is deprecated as of Spring 5.0, I have changed it to WebMvcConfigurationSupport, but I get null values in response.
{
"key": null,
"value": null,
"name":"test"
}
If I change it back to WebMvcConfigurerAdapter, I get the expected response:
{
"name":"test"
}
Spring version: 5.7.0
Jackson Version: 2.9.7
I have googled a lot of things but still no luck. I don't want to use the deprecated class.
Tried removing #EnableWebMvc annotation after adding WebMvcConfigurationSupport as it's not required.
I have overridden configureMessageConverters where I setup MappingJackson2HttpMessageConverter with the inclusion NOT_NULL
#Configuration
#EnableAspectJAutoProxy
#EnableSwagger2
#PropertySource("classpath:test.properties")
#ComponentScan(basePackages = {"com.test.web"})
public class UmwWebConfig extends WebMvcConfigurationSupport {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
ByteArrayHttpMessageConverter bahHumbug = new ByteArrayHttpMessageConverter();
bahHumbug.setSupportedMediaTypes(Collections.singletonList(MediaType.parseMediaType("application/pdf")));
converters.add(bahHumbug);
final MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
final ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
objectMapper.getFactory().configure(JsonGenerator.Feature.ESCAPE_NON_ASCII, true);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
converter.setObjectMapper(objectMapper);
converters.add(converter);
super.configureMessageConverters(converters);
}
Want to remove the null fields from the response. Please let me know if anything is wrong in the configuration.
Don't extend WebMvcConfigurationSupport as that is not equivalent to extending the deprecated WebMvcConfigurerAdapter.
Instead implement WebMvcConfigurer which is also what is suggested in the deprecation documentation in the javadoc of `WebMvcConfigurerAdapter.
Deprecated.
as of 5.0 WebMvcConfigurer has default methods (made possible by a Java 8 baseline) and can be implemented directly without the need for this adapter
So your configuration class header should look something like this
#Configuration
#EnableAspectJAutoProxy
#EnableSwagger2
#EnableWebMvc
#PropertySource("classpath:test.properties")
#ComponentScan(basePackages = {"com.test.web"})
public class UmwWebConfig implements WebMvcConfigurer { ... }
You need to add #EnableWebMvc again (as it should` and override/implement the interface method you need to use. In thise case probably only a single one.
PRO-TIP: Use the Jackson2ObjectMapperBuilder to build the ObjectMapper and use the constructor of the MappingJackson2HttpMessageConverter. This saves creating an additional ObjectMapper (which happens in the default constructor).
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
ByteArrayHttpMessageConverter bahHumbug = new ByteArrayHttpMessageConverter();
bahHumbug.setSupportedMediaTypes(Collections.singletonList(MediaType.parseMediaType("application/pdf")));
converters.add(bahHumbug);
final ObjectMapper objectMapper =
Jackson2ObjectMapperBuilder.json()
.serializationInclusion(JsonInclude.Include.NON_NULL)
.failOnUnknownProperties(true)
.featuresToEnable(JsonGenerator.Feature.ESCAPE_NON_ASCII)
.build();
converters.add(new MappingJackson2HttpMessageConverter(objectMapper);
super.configureMessageConverters(converters);
}

Adding #EnableWebMvc in Springboot changes date output from string to array

I Cretae one springboot(2.0.6.RELEASE) and put in my application.yml the configuration:
jackson:
date-format: dd/MM/yyyy
so when my user call my API every date return just like this:
"vencimento": "01/07/2017"
so, now I put an CORS in my project
#Configuration
#EnableWebMvc
public class CorsConfig implements WebMvcConfigurer {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**");
}
}
but now, all my date filed return just like this:
"vencimento": [
2017,
1,
7
],
anyone know why? and how to fix this? tks
With adding #EnableWebMvc you're saying to Spring that you want to get the full control over Spring MVC configuration. So you need to configure the Jackson object mapper manually.
The main thing is to add these settings:
objectMapper.registerModule(new JavaTimeModule());
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
By default, SerializationFeature.WRITE_DATES_AS_TIMESTAMPS is enabled, and it gives an array of date-time components similar to yours (f.e., [2014,3,30,12,30,23,123456789] instead of "2014-03-30T12:30:23.123456789").
The example of how to configure object mapper and register it as bean in #Configuration class (if you don't familiar with how to do it):
#Bean
public ObjectMapper jsonObjectMapper() {
final ObjectMapper jsonMapper = new ObjectMapper();
jsonMapper.registerModule(new JavaTimeModule());
jsonMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
//some other configuration like:
jsonMapper.registerModule(new Jdk8Module());
jsonMapper.disable(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS);
return jsonMapper;
}
As CROSP said that enabling #EnableWebMvc will turn off default settings in Spring Boot. Here is how I config CORS without using #EnableWebMvc annotation:
#Configuration
class CorsConfig {
#Bean
fun corsConfigurer() = object : WebMvcConfigurer {
override fun addCorsMappings(registry: CorsRegistry) {
registry.addMapping("/**").allowedOrigins("*")
super.addCorsMappings(registry)
}
}
}
Reference: Spring Guide

How to customise Jackson in Spring Boot 1.4

I've been unable to find examples of how to use Jackson2ObjectMapperBuilderCustomizer.java in spring boot 1.4 to customise the features of Jackson.
The doco for customising Jackson in boot 1.4 - https://docs.spring.io/spring-boot/docs/1.4.x/reference/htmlsingle/#howto-customize-the-jackson-objectmapper
My configuration works, although I am unsure if this is the correct way to customise the object mapper using Jackson2ObjectMapperBuilderCustomizer.java
#Configuration
public class JacksonAutoConfiguration {
#Autowired
private Environment env;
#Bean
public Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder(
List<Jackson2ObjectMapperBuilderCustomizer> customizers) {
Jackson2ObjectMapperBuilder builder = configureObjectMapper();
customize(builder, customizers);
return builder;
}
private void customize(Jackson2ObjectMapperBuilder builder,
List<Jackson2ObjectMapperBuilderCustomizer> customizers) {
for (Jackson2ObjectMapperBuilderCustomizer customizer : customizers) {
customizer.customize(builder);
}
}
private Jackson2ObjectMapperBuilder configureObjectMapper() {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
List<String> activeProfiles = asList(env.getActiveProfiles());
if (activeProfiles.contains(SPRING_PROFILE_DEVELOPMENT)) {
builder.featuresToEnable(SerializationFeature.INDENT_OUTPUT);
}
return builder;
}
}
To provide some context, this class sits in my own spring starter project for REST services that just auto configures a number of things, like ControllerAdvice and some trivial features like the above.
So my goal is to extend the Jackson configuration rather than to override any configuration provided by boot or other packages.
To customize the Jackson ObjectMapper that's already pre-configured by Spring Boot, I was able to do this (the example here is to add a custom deserializer).
Configuration class:
#SpringBootConfiguration
public class Application {
#Autowired
private BigDecimalDeserializer bigDecimalDeserializer;
...
#Bean
public Jackson2ObjectMapperBuilderCustomizer addCustomBigDecimalDeserialization() {
return new Jackson2ObjectMapperBuilderCustomizer() {
#Override
public void customize(Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder) {
jacksonObjectMapperBuilder.deserializerByType(BigDecimal.class, bigDecimalDeserializer);
}
};
}
...
}
And my custom deserializer, to show how it's picked up by Spring:
#Component
public class BigDecimalDeserializer extends StdDeserializer<BigDecimal> {
public BigDecimalDeserializer() {
super(BigDecimal.class);
}
#Override
public BigDecimal deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
...
}
...
}
It depends on what you're trying to do.
If you want to make some customisations in addition to those that are performed by default then you should create your own Jackson2ObjectMapperBuilderCustomizer implementation and expose it as a bean. What you currently have is a more complex version of this. Rather than having the customisers injected and then calling them yourself, you can just create your own customiser bean and Boot will call it for you.
If you want to take complete control and switch off all of Boot's customisations then create a Jackson2ObjectMapperBuilder or ObjectMapper bean and configure it as required. The builder approach is preferred as this builder is then also used to configure ObjectMappers created by other components such as Spring Data REST.
Looking at your code and taking a step back, you could configure things far more simply by using a profile-specific configuration file (something like application-dev.properties) to enable indenting of Jackson's output. You can read more about that here.
just create an ObjectMapper bean:
#Bean
ObjectMapper objectMapper() {
return Jackson2ObjectMapperBuilder
.json()
.featuresToEnable(MapperFeature.DEFAULT_VIEW_INCLUSION)
.build();
}

How to customise the Jackson JSON mapper implicitly used by Spring Boot?

I'm using Spring Boot (1.2.1), in a similar fashion as in their Building a RESTful Web Service tutorial:
#RestController
public class EventController {
#RequestMapping("/events/all")
EventList events() {
return proxyService.getAllEvents();
}
}
So above, Spring MVC implicitly uses Jackson for serialising my EventList object into JSON.
But I want to do some simple customisations to the JSON format, such as:
setSerializationInclusion(JsonInclude.Include.NON_NULL)
Question is, what is the simplest way to customise the implicit JSON mapper?
I tried the approach in this blog post, creating a CustomObjectMapper and so on, but the step 3, "Register classes in the Spring context", fails:
org.springframework.beans.factory.BeanCreationException:
Error creating bean with name 'jacksonFix': Injection of autowired dependencies failed;
nested exception is org.springframework.beans.factory.BeanCreationException:
Could not autowire method: public void com.acme.project.JacksonFix.setAnnotationMethodHandlerAdapter(org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter);
nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException:
No qualifying bean of type [org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter]
found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}
It looks like those instructions are for older versions of Spring MVC, while I'm looking for a simple way to get this working with latest Spring Boot.
You can configure property inclusion, and numerous other settings, via application.properties:
spring.jackson.default-property-inclusion=non_null
There's a table in the documentation that lists all of the properties that can be used.
If you want more control, you can also customize Spring Boot's configuration programatically using a Jackson2ObjectMapperBuilderCustomizer bean, as described in the documentation:
The context’s Jackson2ObjectMapperBuilder can be customized by one or more Jackson2ObjectMapperBuilderCustomizer beans. Such customizer beans can be ordered (Boot’s own customizer has an order of 0), letting additional customization be applied both before and after Boot’s customization.
Lastly, if you don't want any of Boot's configuration and want to take complete control over how the ObjectMapper is configured, declare your own Jackson2ObjectMapperBuilder bean:
#Bean
Jackson2ObjectMapperBuilder objectMapperBuilder() {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
// Configure the builder to suit your needs
return builder;
}
I am answering bit late to this question, but someone, in future, might find this useful. The below approach, besides lots of other approaches, works best, and I personally think would better suit a web application.
#Configuration
#EnableWebMvc
public class WebConfiguration extends WebMvcConfigurerAdapter {
... other configurations
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.serializationInclusion(JsonInclude.Include.NON_NULL);
builder.propertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);
builder.serializationInclusion(Include.NON_EMPTY);
builder.indentOutput(true).dateFormat(new SimpleDateFormat("yyyy-MM-dd"));
converters.add(new MappingJackson2HttpMessageConverter(builder.build()));
converters.add(new MappingJackson2XmlHttpMessageConverter(builder.createXmlMapper(true).build()));
}
}
A lot of things can configured in applicationproperties. Unfortunately this feature only in Version 1.3, but you can add in a Config-Class
#Autowired(required = true)
public void configureJackson(ObjectMapper jackson2ObjectMapper) {
jackson2ObjectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
}
[UPDATE: You must work on the ObjectMapper because the build()-method is called before the config is runs.]
The documentation states several ways to do this.
If you want to replace the default ObjectMapper completely, define a #Bean of that type and mark it as #Primary.
Defining a #Bean of type Jackson2ObjectMapperBuilder will allow you to customize both default ObjectMapper and XmlMapper (used in MappingJackson2HttpMessageConverter and MappingJackson2XmlHttpMessageConverter respectively).
You can add a following method inside your bootstrap class which is annotated with #SpringBootApplication
#Bean
#Primary
public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) {
ObjectMapper objectMapper = builder.createXmlMapper(false).build();
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
objectMapper.configure(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS, false);
objectMapper.registerModule(new JodaModule());
return objectMapper;
}
spring.jackson.serialization-inclusion=non_null used to work for us
But when we upgraded spring boot version to 1.4.2.RELEASE or higher, it stopped working.
Now, another property spring.jackson.default-property-inclusion=non_null is doing the magic.
in fact, serialization-inclusion is deprecated. This is what my intellij throws at me.
Deprecated: ObjectMapper.setSerializationInclusion was deprecated in
Jackson 2.7
So, start using spring.jackson.default-property-inclusion=non_null instead
I stumbled upon another solution, which is quite nice.
Basically, only do step 2 from the blog posted mentioned, and define a custom ObjectMapper as a Spring #Component. (Things started working when I just removed all the AnnotationMethodHandlerAdapter stuff from step 3.)
#Component
#Primary
public class CustomObjectMapper extends ObjectMapper {
public CustomObjectMapper() {
setSerializationInclusion(JsonInclude.Include.NON_NULL);
configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
}
Works as long as the component is in a package scanned by Spring. (Using #Primary is not mandatory in my case, but why not make things explicit.)
For me there are two benefits compared to the other approach:
This is simpler; I can just extend a class from Jackson and don't need to know about highly Spring-specific stuff like Jackson2ObjectMapperBuilder.
I want to use the same Jackson configs for deserialising JSON in another part of my app, and this way it's very simple: new CustomObjectMapper() instead of new ObjectMapper().
When I tried to make ObjectMapper primary in spring boot 2.0.6 I got errors
So I modified the one that spring boot created for me
Also see https://stackoverflow.com/a/48519868/255139
#Lazy
#Autowired
ObjectMapper mapper;
#PostConstruct
public ObjectMapper configureMapper() {
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
mapper.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true);
mapper.configure(MapperFeature.ALLOW_COERCION_OF_SCALARS, true);
mapper.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true);
SimpleModule module = new SimpleModule();
module.addDeserializer(LocalDate.class, new LocalDateDeserializer());
module.addSerializer(LocalDate.class, new LocalDateSerializer());
mapper.registerModule(module);
return mapper;
}
The right way to add further configurations to the Spring Boot peconfigured ObjectMapper is to define a Jackson2ObjectMapperBuilderCustomizer. Else you are overwriting Springs configuration, which you do not want to lose.
#Configuration
public class MyJacksonConfigurer implements Jackson2ObjectMapperBuilderCustomizer {
#Override
public void customize(Jackson2ObjectMapperBuilder builder) {
builder.deserializerByType(LocalDate.class, new MyOwnJsonLocalDateTimeDeserializer());
}
}
I found the solution described above with :
spring.jackson.serialization-inclusion=non_null
To only work starting at the 1.4.0.RELEASE version of spring boot. In all other cases the config is ignored.
I verified this by experimenting with a modification of the spring boot sample "spring-boot-sample-jersey"
I've seen numerous questions regarding this issue. This is what worked for me in Spring Boot version 2.7.0-SNAPSHOT.
I created a configuration, MapperConfigs, created a objectMapper bean, annotated primary as the documentation says
#Configuration
#Log4j2
public class MapperConfigs {
#Bean
#Primary
ObjectMapper objectMapper() {
log.info("Object mapper overrides ");
return JsonMapper.builder()
.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES)
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.build();
}
}
I then #Autowired objectMapper. See below:
#Service
public class GenerateRequestUniqueID {
#Autowired
ObjectMapper objectMapper;
...
}
I know the question asking for Spring boot, but I believe lot of people looking for how to do this in non Spring boot, like me searching almost whole day.
Above Spring 4, there is no need to configure MappingJacksonHttpMessageConverter if you only intend to configure ObjectMapper.
You just need to do:
public class MyObjectMapper extends ObjectMapper {
private static final long serialVersionUID = 4219938065516862637L;
public MyObjectMapper() {
super();
enable(SerializationFeature.INDENT_OUTPUT);
}
}
And in your Spring configuration, create this bean:
#Bean
public MyObjectMapper myObjectMapper() {
return new MyObjectMapper();
}
Remove the default one and add your custom converter:
#EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
#Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
// Remove the default MappingJackson2HttpMessageConverter
converters.removeIf(converter -> {
String converterName = converter.getClass().getSimpleName();
return converterName.equals("MappingJackson2HttpMessageConverter");
});
// Add your custom MappingJackson2HttpMessageConverter
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
converter.setObjectMapper(objectMapper);
converters.add(converter);
WebMvcConfigurer.super.extendMessageConverters(converters);
}
}
Note: Please don't use configureMessageConverters() instead of extendMessageConverters() from WebMvcConfigurer because configure method will remove all the existing converters which will be installed by default.
Hope it will help someone like me who has wasted some hours debugging the issue :)
There are two ways to customize Jackson ObjectMapper:-
Override the default behavior of auto-configured ObjectMapper by Spring Boot
Overwrite the ObjectMapper to have a complete control
Override ObjectMapper
#Configuration
public class CustomJacksonConfig {
#Bean
#Primary
public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) {
return builder.build().setSerializationInclusion(JsonInclude.Include.NON_NULL)
.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true)
.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true)
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.configure(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES, false)
.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
.configure(SerializationFeature.INDENT_OUTPUT, true)
.registerModule(new JavaTimeModule());
}
}
Overwrite ObjectMapper
#Configuration
public class CustomJacksonConfig {
#Bean
#Primary
public ObjectMapper objectMapper() {
return new ObjectMapper()
.setSerializationInclusion(JsonInclude.Include.NON_NULL)
.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true)
.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true)
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.configure(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES, false)
.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
.configure(SerializationFeature.INDENT_OUTPUT, true);
}
}
source: https://codingnconcepts.com/spring-boot/customize-jackson-json-mapper/

Categories