Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom ObjectMapper is not used #1159

Closed
sonallux opened this issue Jul 11, 2024 · 9 comments
Closed

Custom ObjectMapper is not used #1159

sonallux opened this issue Jul 11, 2024 · 9 comments

Comments

@sonallux
Copy link

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.

@sonallux sonallux changed the title Custom ObjectMapper can no longer be used Custom ObjectMapper is not used Jul 11, 2024
@mmaeller
Copy link

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);
  }

@lbilger
Copy link

lbilger commented Jul 17, 2024

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.

@pgehl
Copy link

pgehl commented Jul 17, 2024

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);
}

@ilkengin
Copy link

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);
        });
    }
}

@ivan-sirosh
Copy link

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

@anand188
Copy link

any updates on this issue?

@marcos-fernandez
Copy link

I've been also hit by this issue today 😅 . I think could be a nice fix the solution given by @sonallux

Thanks

@Xyaren
Copy link

Xyaren commented Sep 3, 2024

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.

@olegz
Copy link
Contributor

olegz commented Sep 9, 2024

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);
        }

    }

@olegz olegz closed this as completed Sep 9, 2024
@olegz olegz added the duplicate label Sep 9, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

10 participants