Custom ObjectMapper is not used
sonallux opened this issue ยท 9 comments
Describe the bug
With the latest spring-cloud-function 4.1.3 release a custom ObjectMapper
bean is no longer respected when creating the FunctionCatalog
in the ContextFunctionCatalogAutoConfiguration.java.
We need this behaviour, because must configure the ObjectMapper
to use the snake-case naming strategy.
This regression is introduced by commit 8b66fd2 which fixed issue #1148.
Sample
If you really want an example I can provide some.
Possible Solution
I would like to propose the following solution to fix this issue. Adding a @ConditionalOnMissingBean
on the bean definition of the JsonMapper
here. With this change one can easily provide a custom JsonMapper
Bean wrapping a custom ObjectMapper
. This could also fix issue #1059.
We also stumbled over that issue. ๐
You can mitigate it annotating a custom provided bean with @Primary
.
@Bean
@Primary
public JacksonMapper jacksonMapper(final ObjectMapper objectMapper) {
return new JacksonMapper(objectMapper);
}
If I understood the issue correctly, the reason for 8b66fd2 was that you need to configure some things on the ObjectMapper and don't want this to affect the original one from the context. Wouldn't it make sense then to just copy()
the context ObjectMapper before modifying it? This way, you would get all the configuration from the context ObjectMapper like before, but would not change the original configuration.
I will submit a PR. This should restore the behavior from before the GH-1148 change without any additional configuration required. I still think #1160 is a valuable change as it would allow you to further configure the ObjectMapper to be used.
Same here.
We declare several Jackson Modules to add support for some classes serialization and deserialization.
Since upgrading to version 4.13 out tests fail.
I was able to trace the changes between 4.12 and 4.13 in ContextFunctionCatalogAutoConfiguration
4.12: our modules and jackson settings are used
private JsonMapper jackson(ApplicationContext context) {
ObjectMapper mapper;
try {
mapper = context.getBean(ObjectMapper.class);
}
catch (Exception e) {
mapper = new ObjectMapper();
}
mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
mapper.configure(DeserializationFeature.FAIL_ON_TRAILING_TOKENS, true);
return new JacksonMapper(mapper);
}
4.13: our modules and jackson settings are no long used
private JsonMapper jackson(ApplicationContext context) {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
mapper.configure(DeserializationFeature.FAIL_ON_TRAILING_TOKENS, true);
return new JacksonMapper(mapper);
}
We suffer from the same issue as well. As a workaround, we are overriding the behavior of the ObjectMapper
used inside the JacksonMapper
by calling the jacksonMapper.configureObjectMapper method. Hope this solution helps someone out there.
@Configuration
public class CustomJacksonMapperConfig {
@Autowired
public void customJacksonMapperConfig(JacksonMapper jacksonMapper) {
jacksonMapper.configureObjectMapper(objectMapper -> {
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
});
}
}
We also stumbled over that issue. ๐ You can mitigate it annotating a custom provided bean with
@Primary
.@Bean @Primary public JacksonMapper jacksonMapper(final ObjectMapper objectMapper) { return new JacksonMapper(objectMapper); }
it does not work, fails to create a bean.
The bean 'jsonMapper', defined in class path resource [org/springframework/cloud/function/context/config/ContextFunctionCatalogAutoConfiguration$JsonMapperConfiguration.class], could not be registered. A bean with that name has already been defined in com.foo.bar.config.ObjectMapperConfiguration
any updates on this issue?
I've been also hit by this issue today ๐ . I think could be a nice fix the solution given by @sonallux
Thanks
We too are affected.
A workaround for the .. bean with that name has already been defined..
Exception was to create a custom AutoConfiguration that registeres a BeanPostProcessor overwriting the faulty Bean:
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.mycomp.framework.core.util.conditions.ConditionalOnDependencyVersion;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.cloud.function.context.config.ContextFunctionCatalogAutoConfiguration;
import org.springframework.cloud.function.json.JacksonMapper;
import org.springframework.context.annotation.Bean;
@AutoConfiguration
@AutoConfigureBefore({ContextFunctionCatalogAutoConfiguration.class})
public class WorkaroundForCloudFunctions {
private static final Logger LOG = LoggerFactory.getLogger(WorkaroundForCloudFunctions.class);
//TODO remove when https://github.com/spring-cloud/spring-cloud-function/pull/1162 is released and used.
/**
* Background: <a href="https://github.com/spring-cloud/spring-cloud-stream/issues/2977">Github Issue</a>
*/
@ConditionalOnClass(JacksonMapper.class)
@ConditionalOnDependencyVersion(groupId = "org.springframework.cloud",
artifactId = "spring-cloud-function-context",
versionRequirement = "4.1.3")
@Bean
public BeanPostProcessor jacksonMapperFix(ObjectMapper objectMapper) {
return new BeanPostProcessor() {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof JacksonMapper) {
LOG.warn("Injection custom JacksonMapper for spring-cloud-function-context.");
//replicate the modifications of ContextFunctionCatalogAutoConfiguration.JsonMapperConfiguration.jackson
var newOm = objectMapper.copy()
.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false)
.configure(DeserializationFeature.FAIL_ON_TRAILING_TOKENS, true);
return new JacksonMapper(newOm);
}
return bean;
}
};
}
}
Not the cleanest way to do it but effective.
This issue has been addressed
You can configure custom mapper as such
@Configuration
@ConditionalOnProperty(value = "demo.jackson.mapper.enabled", havingValue = "true", matchIfMissing = true)
public static class JacksonConfiguration {
@Bean
@Primary
public JacksonMapper jacksonMapper(final ObjectMapper objectMapper) {
objectMapper.registerModule(new JavaTimeModule());
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
objectMapper.configure(DeserializationFeature.FAIL_ON_TRAILING_TOKENS, true);
return new JacksonMapper(objectMapper);
}
}