Skip to content

MappingJacksonValue and Jackson2CodecSupport#registerObjectMappersForType do not work together #28045

@ghostd

Description

@ghostd

Spring Framework 5.3.15
Spring Boot 2.6.3

I set up 2 ObjectMappers (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:

@Override
public DataBuffer encodeValue(Object value, DataBufferFactory bufferFactory,
ResolvableType valueType, @Nullable MimeType mimeType, @Nullable Map<String, Object> hints) {
ObjectMapper mapper = selectObjectMapper(valueType, mimeType);
if (mapper == null) {
throw new IllegalStateException("No ObjectMapper for " + valueType);
}
Class<?> jsonView = null;
FilterProvider filters = null;
if (value instanceof MappingJacksonValue) {
MappingJacksonValue container = (MappingJacksonValue) value;
value = container.getValue();
jsonView = container.getSerializationView();
filters = container.getFilters();
}
ObjectWriter writer = createObjectWriter(mapper, valueType, mimeType, jsonView, hints);

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"

Metadata

Metadata

Assignees

Labels

in: webIssues in web modules (web, webmvc, webflux, websocket)type: bugA general bug

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions