Spring-boot: Overriding Jackson2JsonDecoder/Encoder should be easier with CodecCustomizer

Created on 13 Nov 2018  路  10Comments  路  Source: spring-projects/spring-boot

To override the default Jackson2JsonEncoder, I have to set to false CodecConfigurer#registerDefaults
and register my custom Jackson2JsonEncoder using CodecConfigurer#customCodecs. Hence I cannot rely anymore on Spring Boot to add the LoggingCodecConfiguration or potential future configuration improvements.

bug

Most helpful comment

@msoub I think you may have to resort to using a BeanPostProcessor that replaces the jacksonCodecCustomizer with one that meets your needs. Something like this:

@Bean
public static BeanPostProcessor replaceJacksonCodeCustomizer() {
    return new BeanPostProcessor() {

        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName)
                throws BeansException {
            if ("jacksonCodecCustomizer".equals(beanName)) {
                return (CodecCustomizer) (configurer) -> {
                    // Customize as needed
                };
            }
            return bean;
        }

    };
}

All 10 comments

I don't think you need to set registerDefaults to false for that; have you tried customizing the default Jackson codec with:

codecConfigurer.defaultCodecs().jackson2JsonEncoder(customJacksonEncoder);

Don't forget you can @Order a CodecCustomizer bean declaration to run before/after other customizers.

Could you let me know if I'm missing something here?

@bclozel Yes it was what I did first. It seems that I'm able to execute my filter before or after the LoggingCodecConfiguration but CodecCustomizer#jacksonCodecCustomizer is always registered last and overrides my configuration

I think I get it now. Because we're strictly applying the @Order and that in that case the last one wins, we should probably apply the CodecCustomizer beans in reverse order here.

FWIW, I think it would be confusing if we reverse their order as we don't do that anywhere else. It would be more consistent with the approach elsewhere if we gave jacksonCodeCustomizer an order higher than lowest precedence so that a user-provided customiser can go after it. We've used 0 elsewhere for that higher precedence.

We should probably order our own at 0, indeed.
This would solve this particular instance, but IMO if developers define a customizer with @Order(Ordered.HIGHEST_PRECEDENCE), they probably don't expect it to be overridden by our auto-configuration. In general, are we expecting users to define customizers at LOWEST_PRECEDENCE if they want to override our auto-configuration?

Yes It's a bit confusing that LOWEST_PRECEDENCE overrides other ordering

I think it could be argued both ways.

On the one hand, LOWEST_PRECEDENCE is just saying that something should go last. It's an implementation detail of how that something is implemented as to whether or not it overrides or backs off. Lowest precedence is also the default order so adding a customiser with no order will go last and win by default. On the other hand, I don't think it's unreasonable to expect something with HIGHEST_PRECEDENCE to "win".

The point I was trying to make above is that we should ensure that things are consistent. As things stand all customisers are, I believe, run in their unreversed order. If we want to change that approach we can consider doing so, but it would need to be done everywhere and in a new minor or even major release. For this particular issue that we'd like to fix in 2.0.x, switching to 0 for our customiser will brings things into line with what we've done elsewhere.

For now what is the best way to override the default Jackson2JsonEncoder? I'd like to override org.springframework.http.codec.json.Jackson2CodecSupport#getHints in order to dynamically inject a JsonView through the dedicated hint.

@msoub I think you may have to resort to using a BeanPostProcessor that replaces the jacksonCodecCustomizer with one that meets your needs. Something like this:

@Bean
public static BeanPostProcessor replaceJacksonCodeCustomizer() {
    return new BeanPostProcessor() {

        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName)
                throws BeansException {
            if ("jacksonCodecCustomizer".equals(beanName)) {
                return (CodecCustomizer) (configurer) -> {
                    // Customize as needed
                };
            }
            return bean;
        }

    };
}

Thank you for the suggestion, I can now inject the JSON_VIEW_HINT.
I'm still trying to figure out a way to inject a view based on the request. I'd like to do something similar to GraphQL where the API consumer specifies the view he wants. With spring-web I was able to achieve that through a custom AbstractMappingJacksonResponseBodyAdvice.

Was this page helpful?
0 / 5 - 0 ratings