Configuring Jackson mixin in Spring Boot application
Asked Answered
C

3

8

I created a mixin for my class. The mixin itself works fine, it's not the issue that most people have where they mix faterxml/codehaus annotations. I tested it in a unit test, creating the ObjectMapper "by hand" while using the addMixIn method - it worked just fine.

I want to use that mixin to modify the response jsons returned from my REST endpoints. I've tried to customize Spring Boot's ObjectMapper in many different ways:

BuilderCustomizer:

@Bean
public Jackson2ObjectMapperBuilderCustomizer addMixin(){
    return new Jackson2ObjectMapperBuilderCustomizer() {
        @Override
        public void customize(Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder) {
            jacksonObjectMapperBuilder.mixIn(MyClass.class, MyClassMixin.class);                
        }
    };
}

Builder:

@Bean
Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder() {
    return new Jackson2ObjectMapperBuilder().mixIn(MyClass.class, MyClassMixin.class);
}

Converter:

@Bean
public MappingJackson2HttpMessageConverter configureJackson(){
    MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
    ObjectMapper mapper = new ObjectMapper();
    mapper.addMixIn(MyClass.class, MyClassMixin.class);
    converter.setObjectMapper(mapper);
    return converter;
}

ObjectMapper:

@Autowired(required = true)
public void configureJackon(ObjectMapper jsonMapper){
    jsonMapper.addMixIn(MyClass.class, MyClassMixin.class);
}

None of these work.

Chutzpah answered 5/7, 2018 at 13:40 Comment(0)
M
8

As of Spring Boot 2.7, there is built-in support for mixins.

Adding the following annotation:

@JsonMixin(MyClass::class)
class MyClassMixin{

will register mixin in the auto-configured ObjectMapper.

Musaceous answered 30/5, 2022 at 14:51 Comment(0)
S
3

This might depend on Spring Boot version but as per Customize the Jackson ObjectMapper defining a new Jackson2ObjectMapperBuilderCustomizer bean is sufficient

The context’s Jackson2ObjectMapperBuilder can be customized by one or more Jackson2ObjectMapperBuilderCustomizer beans. Such customizer beans can be ordered (Boot’s own customizer has an order of 0), letting additional customization be applied both before and after Boot’s customization.

Splenic answered 5/7, 2018 at 13:46 Comment(0)
P
1

I had tried the above and it did not work for me either. While debugging, I noticed that the ObjectMapper inside the message converter was null.

Referring to the post get registered message converters, I ended up replacing the default message converter for Jackson, allowing me to customize the object mapper to my needs:

@SpringBootApplication
@RestController
public class MixinTest {

    public static void main(String[] args) {
        SpringApplication.run(MixinTest.class, args);
    }

    static class Person {
        private String title;
        private String name;
        private String nullField;
        private LocalDate date;

        Person(String title, String name) {
            this.title = title;
            this.name = name;
            this.date = LocalDate.now();
        }
        // getters here...
    }

    // this will exclude nullField 
    @JsonInclude(JsonInclude.Include.NON_NULL)
    interface PersonMixin {
        @JsonProperty("fullName")
        String getName();
    }

    @Bean
    public Jackson2ObjectMapperBuilderCustomizer personCustomizer() {
        return jacksonObjectMapperBuilder ->
            jacksonObjectMapperBuilder.mixIn(Person.class, PersonMixin.class);
    }

    @Bean
    public MappingJackson2HttpMessageConverter myMessageConverter(
            // provided by Spring
            RequestMappingHandlerAdapter reqAdapter,
            Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder) {
        ObjectMapper mapper = jacksonObjectMapperBuilder
            .featuresToEnable(SerializationFeature.INDENT_OUTPUT)
            .featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
            .modulesToInstall(new JavaTimeModule())
            .build();

        // **replace previous MappingJackson converter**
        List<HttpMessageConverter<?>> converters = 
            reqAdapter.getMessageConverters();
        converters.removeIf(httpMessageConverter ->
            httpMessageConverter.getClass()
                .equals(MappingJackson2HttpMessageConverter.class));

        MappingJackson2HttpMessageConverter jackson = new 
            MappingJackson2HttpMessageConverter(mapper);
        converters.add(jackson);
        reqAdapter.setMessageConverters(converters);
        return jackson;
    }

    @GetMapping("/test")
    public Person get() {
        return new Person("Mr", "Joe Bloggs");
    }
}

Which outputs the following in the browser after hitting http://localhost:8080/test:

{
    "title" : "Mr",
    "date" : "2019-09-03",
    "fullName" : "Joe Bloggs"
}

This way, I should be able to add as many customizers as necessary. I'm sure there's a better way to do this. It seems hacky to replace internals like this...

Peristyle answered 3/9, 2019 at 17:4 Comment(1)
Thanks for this example! It works great we don't don't have control over the client JDK!Spelldown

© 2022 - 2025 — McMap. All rights reserved.