FluxMappingJacksonValue.zip Affects: \5.3.3


When trying to filter Rest Controller Flux output using MappingJacksonValue, you receive an exception instead of filtered JSON.

org.springframework.core.codec.CodecException: Type definition error: [simple type, class com.example.Example]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot resolve PropertyFilter with id 'columns'; no FilterProvider configured (through reference chain: java.util.ArrayList[0]->org.springframework.http.converter.json.MappingJacksonValue["value"])

If you collect the Flux to Mono, and apply MappingJacksonValue to it - it works as expected.

Minimal project for reproducing the issue, with tests, is attached.

Comment From: poutsma

When marshaling non-streaming JSON, Spring WebFlux will convert a Flux<T> into a Mono<List<T>> first, and then marshals that list as a whole in order to be more efficient.

    @GetMapping("/example-flux")
    public Flux<MappingJacksonValue> exampleFlux() {
        SimpleBeanPropertyFilter propertyFilter = SimpleBeanPropertyFilter.filterOutAllExcept("foo");
        FilterProvider filters = new SimpleFilterProvider().addFilter("columns", propertyFilter);

        return Flux.just(new Example("foo", "bar"))
                .map(MappingJacksonValue::new)
                .doOnNext(value -> value.setFilters(filters))
                ;

    }

In the exampleFlux method of your sample, this means that the flux of MappingJacksonValue objects are turned in a list, and passed on to Jackson. Jackson then tries to find the property filter 'columns', but can't find it, because that filter is configured on the MappingJacksonValue objects in the list; not the list itself.

    @GetMapping("/example-mono")
    public Mono<MappingJacksonValue> exampleMono() {
        SimpleBeanPropertyFilter propertyFilter = SimpleBeanPropertyFilter.filterOutAllExcept("foo");
        FilterProvider filters = new SimpleFilterProvider().addFilter("columns", propertyFilter);

        return Flux.just(new Example("foo", "bar"))
                .collectList()
                .map(MappingJacksonValue::new)
                .doOnNext(value -> value.setFilters(filters))
                ;

    }

The exampleMono method essentially does the same thing, but in a different order, which does allow WebFlux to find the filter.

I am not entirely sure if and how MappingJacksonValue is supposed to be used with Flux instances (or any multi-value publisher really). I could consider something like the following:

    @GetMapping("/example-mapping-value")
    public MappingJacksonValue exampleMappingValue() {
        SimpleBeanPropertyFilter propertyFilter = SimpleBeanPropertyFilter.filterOutAllExcept("foo");
        FilterProvider filters = new SimpleFilterProvider().addFilter("columns", propertyFilter);

        Flux<Example> flux = Flux.just(new Example("foo", "bar"));
        MappingJacksonValue value = new MappingJacksonValue(flux);
        value.setFilters(filters);
        return value;
    }

but this is currently not supported and would non-trivial to add. Do you have any thoughts, @rstoyanchev ?

Comment From: poutsma

After some discussion, we have concluded that neither Flux<MappingJacksonValue<Example>> nor MappingJacksonValue<Flux<Example>> are supported as return value from a controller. The former would not be a good model to begin with, since the wrapper applies to the whole series. For the latter, there is a good workaround in the form of your exampleMono method.

Comment From: kzander91

@poutsma I was wondering if we could revisit this issue with Spring Framework 6, since the AbstractJackson2Encoder no longer uses collectList() when encoding a Flux and serializes each item individually, even in non-streaming cases:

https://github.com/spring-projects/spring-framework/blob/0abadaa2f65479e77a4ab4201c620c50f9118950/spring-web/src/main/java/org/springframework/http/codec/json/AbstractJackson2Encoder.java#L156-L215

In my application, I need MappingJacksonValue and am encoding very large sequences where collectList() can be problematic w.r.t. memory usage.

Comment From: sdeleuze

Answered here.