Description
Spring Framework 5.3.15
Spring Boot 2.6.3
I set up 2 ObjectMapper
s (one per api version) : the last version uses the default ObjectMapper
(created by Spring Boot), and i instantiate an other ObjectMapper
for the version 1 (there is different settings for the dates, the null fields, and so on).
I also need to build a Jackson Filter at runtime (the filter depends on the roles of the authenticated user), for that i can use the MappingJacksonValue
wrapper. But when the values are wrapped, Spring will always use the default ObjectMapper
.
We can see here that the ObjectMapper
is selected before unwraping the value:
Is that "by design" or is this a missing feature?
Sample code:
@Configuration
public class Config {
private static final MimeType[] EMPTY_MIME_TYPES = {};
@Bean
CodecCustomizer myJacksonCodecCustomizer(ObjectMapper objectMapper) {
return (configurer) -> {
CodecConfigurer.DefaultCodecs defaults = configurer.defaultCodecs();
defaults.jackson2JsonDecoder(new Jackson2JsonDecoder(objectMapper, EMPTY_MIME_TYPES));
Jackson2JsonEncoder jackson2JsonEncoder = new Jackson2JsonEncoder(objectMapper, EMPTY_MIME_TYPES);
// API v2 will use the default object mapper
jackson2JsonEncoder.registerObjectMappersForType(Controller.HelloV1.class, map -> {
map.put(MediaType.APPLICATION_JSON, mapperForApiV1());
});
defaults.jackson2JsonEncoder(jackson2JsonEncoder);
};
}
private ObjectMapper mapperForApiV1() {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.featuresToEnable(SerializationFeature.WRITE_DATES_WITH_ZONE_ID);
builder.serializationInclusion(JsonInclude.Include.NON_ABSENT);
builder.modules(new SimpleModule(), new JavaTimeModule());
// And other settings
return builder.build();
}
}
@RestController
public class Controller {
@GetMapping("/v1/hello")
public Mono<HelloV1> hello1() {
return Mono.just(new HelloV1("world", true, null));
}
@GetMapping("/v2/hello")
public Mono<HelloV2> hello2() {
return Mono.just(new HelloV2("world", true, null));
}
@GetMapping("/v1/wrapped-hello")
public Mono<MappingJacksonValue> wrappedHello1() {
MappingJacksonValue mappingJacksonValue = new MappingJacksonValue(new HelloV1("world", true, null));
// mappingJacksonValue.setFilters(buildFilterFromRoles());
return Mono.just(mappingJacksonValue);
}
@GetMapping("/v2/wrapped-hello")
public Mono<MappingJacksonValue> wrappedHello2() {
MappingJacksonValue mappingJacksonValue = new MappingJacksonValue(new HelloV2("world", true, null));
// mappingJacksonValue.setFilters(buildFilterFromRoles());
return Mono.just(mappingJacksonValue);
}
private FilterProvider buildFilterFromRoles() {
// The actual filter is configured according to the roles of the authenticated user
SimpleBeanPropertyFilter theFilter = SimpleBeanPropertyFilter
.serializeAllExcept("canBeMasked");
return new SimpleFilterProvider().addFilter("myFilter", theFilter);
}
public record HelloV1(String hello, boolean canBeMasked, String nullField) {}
public record HelloV2(String hi, boolean canBeMasked, String nullField) {}
}
Expected results:
"/v1/wrapped-hello" should return the same serialization than "/v1/hello"