I have a Spring Boot web app with several @RestController
classes.
I like the default json format returned by my REST controllers.
For use in my DAO beans (which do json serialization and deserialization ), I have created a custom ObjectMapper
:
@Configuration
public class Config{
@Bean
public ObjectMapper getCustomObjectMapper() {
final ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.setPropertyNamingStrategy(new PropertyNamingStrategy.SnakeCaseStrategy());
return objectMapper;
}
}
And in each of my DAO classes I autowire my custom ObjectMapper
:
@Repository
@Transactional
public class MyDaoImpl implements MyDao {
@Autowired
ObjectMapper objectMapper
//Dao implementation...
}
This all works fine. The problem is that my custom ObjectMapper
gets automatically picked up by Spring and is used for serializing REST responses.
This is undesirable. For REST controllers I want to keep the ObjectMapper
that Spring creates by default.
How can I tell Spring Boot to not detect and not use my custom ObjectMapper
bean for its own internal workings?
The Simone Pontiggia answer is in the correct direction. You should create one @Primary bean, which Spring will use in its internals, and then to create your own ObjectMapper beans and autowired them using @Qualifier.
The problem here is that, creating default bean like:
@Bean
@Primary
public ObjectMapper objectMapper() {
return new ObjectMapper();
}
Won't actually work as expected, because the Spring default ObjectMapper has additional configurations. The correct way to create default ObjectMapper that will be used by spring, is:
@Bean
@Primary
public ObjectMapper objectMapper() {
return Jackson2ObjectMapperBuilder.json().build();
}
You can find more information about the Spring default ObjectMapper here: https://docs.spring.io/spring-boot/docs/current/reference/html/howto-spring-mvc.html under 79.3 Customize the Jackson ObjectMapper
You can provide a standard ObjectMapper and your customized object mapper, and set the standard as @Primary
.
Then gives your custom ObjectMapper a name and use it with @Qualifier annotation.
@Configuration
public class Config{
//This bean will be selected for rest
@Bean
@Primary
public ObjectMapper stdMapper(){
return new ObjectMapper();
}
//You can explicitly refer this bean later
@Bean("customObjectMapper")
public ObjectMapper getCustomObjectMapper() {
final ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.setPropertyNamingStrategy(new PropertyNamingStrategy.SnakeCaseStrategy());
return objectMapper;
}
}
Now you can reference your custom mapper
@Repository
@Transactional
public class MyDaoImpl implements MyDao {
@Autowired
@Qualifier("customObjectMapper")
ObjectMapper objectMapper
//Dao implementation...
}
@Resource("custonmObjectMapper")
will do the same of @Autowired and@Qualifier
together
Since I didn't want to touch Spring's default ObjectMapper
, creating a @Primary
ObjectMapper
to shadow Spring's default ObjectMapper
was out of the question.
Instead, what I ended up doing is creating a BeanFactoryPostProcessor
which registers in Spring's context a custom, non primary ObjectMapper
:
@Component
public class ObjectMapperPostProcessor implements BeanFactoryPostProcessor {
public static final String OBJECT_MAPPER_BEAN_NAME = "persistenceObjectMapper";
@Override
public void postProcessBeanFactory(final ConfigurableListableBeanFactory beanFactory) {
final AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
.genericBeanDefinition(ObjectMapper.class, this::getCustomObjectMapper)
.getBeanDefinition();
// Leave Spring's default ObjectMapper (configured by JacksonAutoConfiguration)
// as primary
beanDefinition.setPrimary(false);
final AutowireCandidateQualifier mapperQualifier = new AutowireCandidateQualifier(PersistenceObjectMapper.class);
beanDefinition.addQualifier(mapperQualifier);
((DefaultListableBeanFactory) beanFactory).registerBeanDefinition(OBJECT_MAPPER_BEAN_NAME, beanDefinition);
}
private ObjectMapper getCustomObjectMapper() {
final ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.setPropertyNamingStrategy(new PropertyNamingStrategy.SnakeCaseStrategy());
return objectMapper;
}
}
As can be seen in the code above, I also assigned a qualifier to my custom ObjectMapper
bean.
My qualifier is an annotation which is annotated with @Qualifier
:
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface PersistenceObjectMapper {
}
I can then autowire my custom ObjectMapper
using my custom annotation, like this:
@Repository
public class MyDao {
@Autowired
public MyDao(DataSource dataSource, @PersistenceObjectMapper ObjectMapper objectMapper) {
// constructor code
}
You can create:
public class MapperUtils {
private static final ObjectMapper mapper = new ObjectMapper();
public static <T> T parseResponse(byte[] byteArrray, Class<T> parseType) throws JsonParseException, JsonMappingException, IOException {
return mapper.readValue(byteArrray, parseType);
}
}
ObjectMapper
is thread-safe. However, some people discourage having single instance because of performance issues (Should I declare Jackson's ObjectMapper as a static field? ).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With