I'm trying to have 2 different Jackson ObjectMappers so that I that I can switch between using either one of them in my code. The 2 ObjectMappers are going to have some slight differences in their configuration.
In my configuration file, I have them as 2 separate means this way:
#Configuration
public class ApplicationConfiguration {
private ObjectMapper createMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
// And a few more other common configurations
return mapper;
}
#Bean
#Qualifier("standardObjectMapper")
public ObjectMapper standardObjectMapper() {
ObjectMapper mapper = createMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(Product.class, new StandardProductSerializer());
mapper.registerModule(module);
return mapper;
}
#Bean
#Qualifier("specialObjectMapper")
public ObjectMapper specialObjectMapper() {
ObjectMapper mapper = createMapper();
SimpleModule module = new SimpleModule();
// In this mapper, I have a different serializer for the Product class
module.addSerializer(Product.class, new SpecialProductSerializer());
mapper.registerModule(module);
return mapper;
}
}
My plan is to inject and use one of those mappers whenever I need them. So in my test, I have something like this:
class SerializationTest {
#Autowired
#Qualifier("standardObjectMapper")
private ObjectMapper standardObjectMapper;
#Autowired
#Qualifier("specialObjectMapper")
private ObjectMapper specialObjectMapper;
#Test
void testSerialization() throws JsonProcessingException {
Product myProduct = new Product("Test Product");
String stdJson = objectMapper.writeValueAsString(myProduct);
String specialJson = specialObjectMapper.writeValueAsString(myProduct);
// Both stdJson and specialJson are of the same value even though they should be different because the mappers have different serializers!
}
}
However, it seems that the 2 ObjectMappers, standardObjectMapper and specialObjectMapper both are using the same StandardProductSerializer.
I'm expecting the specialObjectMapper to use the SpecialProductSerializer but this isn't the case.
Shouldn't the 2 ObjectMappers be different? I assume the injection would be based on their individual names since their types are the same?
What should I do to fix the issue so that the 2 ObjectMappers can be using different Serializers?
Update:
I've tried using Qualifiers but I'm getting an error that the beans cannot be found:
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'com.site.SerializationTest': Unsatisfied dependency expressed through field 'standardObjectMapper'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.fasterxml.jackson.databind.ObjectMapper' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {#org.springframework.beans.factory.annotation.Autowired(required=true), #org.springframework.beans.factory.annotation.Qualifier(value="standardObjectMapper")}
Add #Qualifier should resolve your problem
#Bean
#Qualifier("standardMapper")
public ObjectMapper standardObjectMapper() {
ObjectMapper mapper = createMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(Product.class, new StandardProductSerializer());
mapper.registerModule(module);
return mapper;
}
#Bean
#Qualifier("specialMapper")
public ObjectMapper specialObjectMapper() {
ObjectMapper mapper = createMapper();
SimpleModule module = new SimpleModule();
// In this mapper, I have a different serializer for the Product class
module.addSerializer(Product.class, new SpecialProductSerializer());
mapper.registerModule(module);
return mapper;
}
Then add #Qualifier with your #Autowired
#Autowired
#Qualifier("standardMapper")
private ObjectMapper standardObjectMapper;
#Autowired
#Qualifier("specialMapper")
private ObjectMapper specialObjectMapper;
You can use the #Qualifier annotation in order to distinguish the different istances of ObjectMapper. (see: https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#beans-autowired-annotation-qualifiers)
#Qualifier("objectMapper1")
#Autowired
private ObjectMapper objMapper1;
could you please try this, it will inject bean based on name
#Autowired
#Qualifier("standardObjectMapper")
private ObjectMapper standardObjectMapper;
#Autowired
#Qualifier("specialObjectMapper")
private ObjectMapper specialObjectMapper;
hope this will work for you
You can use #Qualifier annotation:
#Qualifier("specialObjectMapper")
#Autowired
private ObjectMapper specialObjectMapper;
#Qualifier("standardMapper")
#Autowired
private ObjectMapper standardObjectMapper;
The problem is that the #Configuration-class is not scanned during DI startup. Normally, spring looks for configuration from the package the Application class (annotated with #SpringBootApplication) resides.
I would suggest moving the #Configuration class in a sub-package of that in which the #SpringBootApplication resides.
There are other ways (e.g. this one described at baelung) to change the root-package for component scanning. I would, however, not recommend doing this unless all other options have been exhausted.
Related
This question already has answers here:
Spring boot two object mapper bean
(3 answers)
What is a NoSuchBeanDefinitionException and how do I fix it?
(1 answer)
Closed 1 year ago.
I know I'm not the first to have this problem, but I'm struggling to create multiple beans of the same type in Spring Boot 2.5.4.
My config:
#Configuration
public class MapperConfig {
#Bean("yamlMapper")
public ObjectMapper yamlMapper() {
return new ObjectMapper(new YAMLFactory());
}
#Bean("jsonMapper")
public ObjectMapper jsonMapper() {
return new ObjectMapper();
}
}
And my service class:
#Service
#RequiredArgsConstructor
public class MapperService {
#Qualifier("jsonMapper")
private final ObjectMapper jsonMapper;
#Qualifier("yamlMapper")
private final ObjectMapper yamlMapper;
}
The error is as follows:
No qualifying bean of type 'com.fasterxml.jackson.databind.ObjectMapper' available: expected single matching bean but found 2: yamlMapper,jsonMapper
I've tried the various combinations of #Bean, #Qualifier, etc. suggested in other SO posts and the docs, but I can't seem to find a way of making Spring autowire by name instead of type. Any help would be greatly appreciated!
SOLUTION:
As pointed out by Youssef, it's not the MapperService which is failing to find the right bean, it's Spring Boot's MappingJackson2HttpMessageConverterConfiguration class. We can't add annotations to that class, so need to resort to using #Primary in our config.
The context loads okay as follows:
#Configuration
public class MapperConfig {
#Bean
public ObjectMapper yamlMapper() {
return new ObjectMapper(new YAMLFactory());
}
#Bean
#Primary
public ObjectMapper jsonMapper() {
return new ObjectMapper();
}
}
#Service
#RequiredArgsConstructor
public class MapperService {
#Autowired
#Qualifier("jsonMapper")
private final ObjectMapper jsonMapper;
#Autowired
#Qualifier("yamlMapper")
private final ObjectMapper yamlMapper;
}
Use the annotation:
#Resource
https://www.baeldung.com/spring-annotations-resource-inject-autowire
Your service:
#Service
public class MapperService {
#Resource(name = "jsonMapper")
private final ObjectMapper jsonMapper;
#Resource(name = "yamlMapper")
private final ObjectMapper yamlMapper;
}
You need to add #Primary annotation to one of your ObjectMapper beans like that
#Primary
#Bean("jsonMapper")
Because if you are using Spring Boot, it is needed by Jackson when trying auto configuration:
Parameter 0 of method mappingJackson2HttpMessageConverter in org.springframework.boot.autoconfigure.http.JacksonHttpMessageConvertersConfiguration$MappingJackson2HttpMessageConverterConfiguration required a single bean, but 2 were found:
But be careful also to as #M.Deinum comment: could also break stuff as this will also (partially) disable auto configuration for the ObjectMapper
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()
);
I have a spring-boot application that exposes a json REST API. For mapping objects to json it uses the built-in jackson ObjectMapper configured by spring-boot.
Now I need to read some data from a yaml file and I found that an easy way to do it is using Jackson - for this I need to declare a different ObjectMapper to convert yaml to objects. I declared this new mapper bean with a specific name to be able to inject it in my service dealing with reading from the yaml file:
#Bean(YAML_OBJECT_MAPPER_BEAN_ID)
public ObjectMapper yamlObjectMapper() {
return new ObjectMapper(new YAMLFactory());
}
But I need a way to tell all the other "clients" of the original json ObjectMapper to keep using that bean. So basically I would need a #Primary annotation on the original bean. Is there a way to achieve this without having to redeclare the original ObjectMapper in my own configuration (I'd have to dig through spring-boot code to find and copy its configuration)?
One solution I found is to declare a FactoryBean for ObjectMapper and make it return the already declared bean, as suggested in this answer. I found by debugging that my original bean is called "_halObjectMapper", so my factoryBean will search for this bean and return it:
public class ObjectMapperFactory implements FactoryBean<ObjectMapper> {
ListableBeanFactory beanFactory;
public ObjectMapper getObject() {
return beanFactory.getBean("_halObjectMapper", ObjectMapper.class);
}
...
}
Then in my Configuration class I declare it as a #Primary bean to make sure it's the first choice for autowiring:
#Primary
#Bean
public ObjectMapperFactory objectMapperFactory(ListableBeanFactory beanFactory) {
return new ObjectMapperFactory(beanFactory);
}
Still, I'm not 100% happy with this solution because it relies on the name of the bean which is not under my control, and it also seems like a hack. Is there a cleaner solution?
Thanks!
You can define two ObjectMapper beans and declare one as primary, e.g.:
#Bean("Your_id")
public ObjectMapper yamlObjectMapper() {
return new ObjectMapper(new YAMLFactory());
}
#Bean
#Primary
public ObjectMapper objectMapper() {
return new ObjectMapper();
}
Once done, you can use your objectmapper bean with #Qualifier annotation, e.g.:
#Autowired
#Qualifier("Your_id")
private ObjectMapper yamlMapper;
Update
You can dynamically add your ObjectMapper to Spring's bean factory at runtime, e.g.:
#Configuration
public class ObjectMapperConfig {
#Autowired
private ConfigurableApplicationContext context;
#PostConstruct
private void init(){
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(ObjectMapper.class);
builder.addConstructorArgValue(new JsonFactory());
DefaultListableBeanFactory factory = (DefaultListableBeanFactory) context.getBeanFactory();
factory.registerBeanDefinition("yamlMapper", builder.getBeanDefinition());
Map<String, ObjectMapper> beans = context.getBeansOfType(ObjectMapper.class);
beans.entrySet().forEach(System.out::println);
}
}
The above code adds a new bean into context without changing the existing bean (sysout prints two beans in the end of init method). You can then use "yamlMapper" as qualifier to autowire it anywhere.
Update 2 (from question author):
The solution suggested in 'Update' works and here's a simplified version:
#Autowired
private DefaultListableBeanFactory beanFactory;
#PostConstruct
private void init(){
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(YAMLMapper.class);
beanFactory.registerBeanDefinition("yamlMapper", builder.getBeanDefinition());
}
Other option is to wrap custom mapper into custom object:
#Component
public class YamlObjectMapper {
private final ObjectMapper objectMapper;
public YamlObjectMapper() {
objectMapper = new ObjectMapper(new YAMLFactory());
}
public ObjectMapper getMapper() {
return objectMapper;
}
}
Unfortunately this approach requires calling getMapper after you inject YamlObjectMapper.
I believe defining explicit primary object mapper for MVC layer should work this way:
#Primary
#Bean
public ObjectMapper objectMapper() {
return Jackson2ObjectMapperBuilder.json().build();
}
All beans that autowire object mapper via type will use above bean. Your Yaml logic can autowire via YAML_OBJECT_MAPPER_BEAN_ID.
I just realized that I don't need to use a FactoryBean, I could just as well declare a regular bean as #Primary and make it return the original bean, like this:
#Bean
#Primary
public ObjectMapper objectMapper(#Qualifier("_halObjectMapper") ObjectMapper objectMapper) {
return objectMapper;
}
This makes the configuration slightly cleaner, but still requires the exact name of the original ObjectMapper. I guess I'll stay with this solution, though.
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/
I can't seem to get my Jackson ObjectMapper Module registered correctly.
I'm using a Guice + Jersey + Jackson (FasterXML) stack.
I've followed how to customise the ObjectMapper based on various question here. In particular, I have a ContextResolver declared, marked as an #javax.ws.rs.ext.Provider and a #javax.inject.Singleton.
I have a GuiceServletContextListener along the lines of:
#Override
protected Injector getInjector() {
Injector injector = Guice.createInjector(new DBModule(dataSource),
new ServletModule()
{
#Override
protected void configureServlets() {
// Mapper
bind(JacksonOMP.class).asEagerSingleton();
// ...
Map<String, String> initParams = new HashMap<String, String>();
initParams.put("com.sun.jersey.config.feature.Trace",
"true");
initParams.put("com.sun.jersey.api.json.POJOMappingFeature", "true");
serve("/services/*").with(
GuiceContainer.class,
initParams);
}
});
return injector;
}
Mapper defined
import javax.inject.Singleton;
import javax.ws.rs.Produces;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;
#Provider
#Singleton
#Produces
public class JacksonOMP implements ContextResolver<ObjectMapper> {
#Override
public ObjectMapper getContext(Class<?> aClass) {
final ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new Hibernate4Module());
return mapper;
}
}
However - with this configuration alone, getContext() is never called, so the mapper is never registered. I'm stuck in a typical guice--annotations-mystery, where it's practically untraceable to what I'm actually supposed to be doing. Spring users just report registering the component, and the container just picks it up.
This answer talks about overriding my own javax.ws.rs.core.Application implementation. However, this looks hard-wired in the jersey-guice impementation of GuiceContainer to be DefaultResourceConfig():
#Override
protected ResourceConfig getDefaultResourceConfig(Map<String, Object> props,
WebConfig webConfig) throws ServletException {
return new DefaultResourceConfig();
}
Am I supposed subclass GuiceContainer here? Or is there some other magic annotation that I'm missing?
This seems a fairly common thing to want to do - I'm surprised at how hard it's proving to do with this guice combination.
I'm stuck in a typical guice--annotations-mystery, where it's
practically untraceable to what I'm actually supposed to be doing.
Spring users just report registering the component, and the container
just picks it up.
You really should read excellent Guice documentation. Guice is very easy to use, it has very small number of basic concepts. Your problem is in that you mixed Jersey JAX-RS dependency injection and Guice dependency injection. If you are using GuiceContainer then you declare that you will be using Guice for all of your DI, so you have to add bindings with Guice and not with JAX-RS.
For instance, you do not need ContextResolver, you should use plain Guice Provider instead:
import com.google.inject.Provider;
public class ObjectMapperProvider implements Provider<ObjectMapper> {
#Override
public ObjectMapper get() {
final ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new Hibernate4Module());
return mapper;
}
}
Then you should add corresponding binding to your module:
bind(ObjectMapper.class).toProvider(ObjectMapperProvider.class).in(Singleton.class);
This will bind ObjectMapper, but it is not enough to use Jersey with Jackson. You will need some kind of MessageBodyReader/MessageBodyWriter, e.g. JacksonJsonProvider. You will need another provider for it:
public class JacksonJsonProviderProvider implements Provider<JacksonJsonProvider> {
private final ObjectMapper mapper;
#Inject
JacksonJsonProviderProvider(ObjectMapper mapper) {
this.mapper = mapper;
}
#Override
public JacksonJsonProvider get() {
return new JacksonJsonProvider(mapper);
}
}
Then bind it:
bind(JacksonJsonProvider.class).toProvider(JacksonJsonProviderProvider.class).in(Singleton.class);
This is all you need to do - no subclassing is needed.
There is a room for code size optimization though. If I were you I would use #Provides-methods:
#Provides #Singleton
ObjectMapper objectMapper() {
final ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new Hibernate4Module());
return mapper;
}
#Provides #Singleton
JacksonJsonProvider jacksonJsonProvider(ObjectMapper mapper) {
return new JacksonJsonProvider(mapper);
}
These methods should be added to one of your modules, e.g. to anonymous ServletModule. Then you won't need separate provider classes.
BTW, you should use JerseyServletModule instead of plain ServletModule, it provides a lot of useful bindings for you.