25

I have mixins configured in my objectmapperbuilder config, using the regular spring web controller, the data outputted according to the mixins. However using webflux, a controller with a method returning a Flow or Mono have the data serialized like if the objectmapper a default one.

How to get webflux to enforce an objectmapper configuration to be used ?

sample config:

@Bean
JavaTimeModule javatimeModule(){
    return new JavaTimeModule();
}

@Bean
Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer(){
return jacksonObjectMapperBuilder ->  jacksonObjectMapperBuilder.featuresToEnable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
                                                                    .mixIn(MyClass.class, MyClassMixin.class);
}
user1568967
  • 1,816
  • 2
  • 16
  • 18

6 Answers6

22

I actually found my solution by stepping through the init code:

@Configuration
public class Config {

    @Bean
    JavaTimeModule javatimeModule(){
        return new JavaTimeModule();
    }

    @Bean
    Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer(){
    return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder.featuresToEnable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
            .mixIn(MyClass.class, MyClassMixin.class);
    }


    @Bean
    Jackson2JsonEncoder jackson2JsonEncoder(ObjectMapper mapper){
       return new Jackson2JsonEncoder(mapper);
    }

    @Bean
    Jackson2JsonDecoder jackson2JsonDecoder(ObjectMapper mapper){
        return new Jackson2JsonDecoder(mapper);
    }

    @Bean
    WebFluxConfigurer webFluxConfigurer(Jackson2JsonEncoder encoder, Jackson2JsonDecoder decoder){
        return new WebFluxConfigurer() {
            @Override
            public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
                configurer.defaultCodecs().jackson2JsonEncoder(encoder);
                configurer.defaultCodecs().jackson2JsonDecoder(decoder);
            }
        };

    }
}
chris
  • 2,467
  • 2
  • 25
  • 25
user1568967
  • 1,816
  • 2
  • 16
  • 18
  • 8
    `@EnableWebFlux` will deactivate all the webflux auto-configuration, so I wouldn't use it here. – Brian Clozel Apr 10 '17 at 08:17
  • 1
    When used `@EnableWebFlux` to customize the webflux configuration, the configuration class can be extended from `WebFluxConfigurer ` directly to simplify the configuration work. – Hantsy Sep 10 '17 at 12:09
  • If the configuration class extended from `WebFluxConfigurer`, you need to inject a ObjectMapper. The solution of @user1568967 is perfect. – RJ.Hwang Aug 25 '18 at 05:27
  • settings from jackson2ObjectMapperBuilderCustomizer are not picked up, i had to use configureHttpMessageCodecs – razor Nov 19 '18 at 14:41
  • 1
    I tried this and 2 other suggestions below but none of them is working for me. Maybe this has to do with the fact that I'm using `WebClient` to fetch streaming data (text/event-stream)? Simply injecting `objectMapper` and using it directly via e.g. `mapper.readValue("{\"datetime\":1613047019569}", Timestamp.class)` works as expected but WebClient is simply ignoring all Jackson customization... – msilb Feb 20 '20 at 20:58
  • Same. None of the solutions so far worked. I'm providing a customized `ObjectMapper`, but it seems to be completely ignored. I wonder if it's because I'm using `WebClient.mutate`. – David Good Feb 02 '21 at 23:32
15

I translated the solution of @Alberto Galiana to Java and injected the configured Objectmapper for convenience, so you avoid having to do multiple configurations:

@Configuration
@RequiredArgsConstructor
public class WebFluxConfig implements WebFluxConfigurer {

    private final ObjectMapper objectMapper;

    public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
        configurer.defaultCodecs().jackson2JsonEncoder(
            new Jackson2JsonEncoder(objectMapper)
        );

        configurer.defaultCodecs().jackson2JsonDecoder(
            new Jackson2JsonDecoder(objectMapper)
        );
    }
}
geoforce
  • 373
  • 2
  • 8
  • I think `objectMapper` should be annotated with `@AutoWired`. – RJ.Hwang Aug 24 '18 at 16:52
  • 5
    This example uses Constructor injection so the Autowired annotation is not needed. The constructor is generated by the RequiredArgsConstructor Lombok annotation. But you could remove that annotation and add the Autowired one on the field if you remove the final Keyword – geoforce Aug 26 '18 at 16:20
10

Just implement WebFluxConfigurer and override method configureHttpMessageCodecs

Sample code for Spring Boot 2 + Kotlin

@Configuration
@EnableWebFlux
class WebConfiguration : WebFluxConfigurer {

    override fun configureHttpMessageCodecs(configurer: ServerCodecConfigurer) {
        configurer.defaultCodecs().jackson2JsonEncoder(Jackson2JsonEncoder(ObjectMapper()
                .setSerializationInclusion(JsonInclude.Include.NON_EMPTY)))

        configurer.defaultCodecs().jackson2JsonDecoder(Jackson2JsonDecoder(ObjectMapper()
                .enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)))
    }
}

Make sure all your data classes to be encoded/decoded have all its properties annotated with @JsonProperty even if property name is equal in class and json data

data class MyClass(
    @NotNull
    @JsonProperty("id")
    val id: String,

    @NotNull
    @JsonProperty("my_name")
    val name: String)
Alberto Galiana
  • 201
  • 5
  • 7
4

In my case, I was trying to use a customized ObjectMapper while inheriting all of the behavior from my app's default WebClient.

I found that I had to use WebClient.Builder.codecs. When I used WebClient.Builder.exchangeStrategies, the provided overrides were ignored. Not sure if this behavior is something specific to using WebClient.mutate, but this is the only solution I found that worked.

WebClient customizedWebClient = webClient.mutate()
                                         .codecs(clientCodecConfigurer -> 
                                                     clientCodecConfigurer.defaultCodecs()
                                                                          .jackson2JsonDecoder(new Jackson2JsonDecoder(customObjectMapper)))
                                         .build();
David Good
  • 486
  • 5
  • 8
0

I have tried all the different solutions (@Primary @Bean for ObjectMapper, configureHttpMessageCodecs(), etc.). What worked for me at the end was specifying a MIME type. Here's an example:

@Configuration
class WebConfig: WebFluxConfigurer {
override fun configureHttpMessageCodecs(configurer: ServerCodecConfigurer) {
        val encoder = Jackson2JsonEncoder(objectMapper, MimeTypeUtils.APPLICATION_JSON)
        val decoder = Jackson2JsonDecoder(objectMapper, MimeTypeUtils.APPLICATION_JSON)
        configurer.defaultCodecs().jackson2JsonEncoder(encoder)
        configurer.defaultCodecs().jackson2JsonDecoder(decoder)
    }
}

VoodooBoot
  • 143
  • 1
  • 2
  • 8
0

As I explained in answer here you can omit bloated codecs configuration and just define bean like this:

@Bean
public WebClient webClient(WebClient.Builder builder) {
    return builder.build();
}

The major issue is that Spring Boot does auto-configure WebClient builder and not actual instance. Instances of WebClient created by hand are using their built-in default mapper and therefore you have to configure codecs by hand.

nouveu
  • 162
  • 4
  • 9