Affects: \<5.3.3>

Hi here is my issue: I think I have found a behavior that might not be as expected regarding the use of @JsonView on a controller method. The behavior does not match the bare mapper.writerWithView(View.class).writeValueAsString(obj) one might expect (?).

I am facing issue when using a controller method defined as such:

@RestController
@RequestMapping("/api/v1/resources")
class MyController {

    @GetMapping
    @JsonView(Views.Base.class)
    Page<MyResource> list(@ModelAttribute ListParams params, @PageableDefault Pageable paging) {
        return service.list(params, paging);
    }
}

Of course Page is given as empty object because none of its fields are marked with the @JsonView(Views.Base.class). So I created a Mix-in interface like follows:

@JsonView(Views.Base.class)
interface PageMixInView<T> extends Page<T> {}

and added it using

@Configuration
public class JacksonConfiguration {

    @Bean
    Jackson2ObjectMapperBuilderCustomizer addPageViewMixIn() {
        return builder -> {
            builder.mixIn(Page.class, PageViewMixIn.class);
        };
    }
}

I have implemented the following tests to demonstrate that it works:

public class MixInDebuggingTest {
    @Nested
    class UnitTest {
        @Test
        void shouldSucceedWithMixIn() throws JsonProcessingException {
            ObjectMapper mapper = new ObjectMapper();
            mapper.disable(MapperFeature.DEFAULT_VIEW_INCLUSION);
            mapper.addMixIn(Page.class, PageViewMixIn.class);

            Page<Obj> page = generateTestData();
            String jsonStr = mapper
                    .writerWithView(Views.Base.class)
                    .withDefaultPrettyPrinter()
                    .writeValueAsString(page);
            assertThat(jsonStr, hasJsonPath("$.content[0].value", is("1")));
            assertThat(jsonStr, hasJsonPath("$.content[1].value", is("2")));
            assertThat(jsonStr, hasJsonPath("$.content[2].value", is("3")));
            assertThat(jsonStr, hasJsonPath("$.totalPages", is(1)));
            assertThat(jsonStr, hasJsonPath("$.totalElements", is(3)));
            assertThat(jsonStr, hasJsonPath("$.last", is(true)));
            assertThat(jsonStr, hasJsonPath("$.size", is(10)));
            assertThat(jsonStr, hasJsonPath("$.number", is(0)));
            assertThat(jsonStr, hasJsonPath("$.numberOfElements", is(3)));
            assertThat(jsonStr, hasJsonPath("$.first", is(true)));
            assertThat(jsonStr, hasJsonPath("$.empty", is(false)));
        }

        @Test
        void shouldFailWithoutMixIn() throws JsonProcessingException {
            ObjectMapper mapper = new ObjectMapper();
            mapper.disable(MapperFeature.DEFAULT_VIEW_INCLUSION);

            Page<Obj> page = generateTestData();
            String jsonStr = mapper
                    .writerWithView(Views.Base.class)
                    .withDefaultPrettyPrinter()
                    .writeValueAsString(page);
            assertThat(jsonStr, not(hasJsonPath("$.content[0].value")));
            assertThat(jsonStr, not(hasJsonPath("$.content[1].value")));
            assertThat(jsonStr, not(hasJsonPath("$.content[2].value")));
            assertThat(jsonStr, not(hasJsonPath("$.totalPages")));
            assertThat(jsonStr, not(hasJsonPath("$.totalElements")));
            assertThat(jsonStr, not(hasJsonPath("$.last")));
            assertThat(jsonStr, not(hasJsonPath("$.size")));
            assertThat(jsonStr, not(hasJsonPath("$.number")));
            assertThat(jsonStr, not(hasJsonPath("$.numberOfElements")));
            assertThat(jsonStr, not(hasJsonPath("$.first")));
            assertThat(jsonStr, not(hasJsonPath("$.empty")));
        }
    }

    @Nested
    @SpringBootTest
    class IntegrationTest{
        @Autowired
        ObjectMapper mapper;
        @Test
        void shouldBeUsingMixin() throws JsonProcessingException {
            Page<Obj> page = generateTestData();
            String jsonStr = mapper
                    .writerWithView(Views.Base.class)
                    .withDefaultPrettyPrinter()
                    .writeValueAsString(page);

            assertThat(jsonStr, hasJsonPath("$.content[0].value", is("1")));
            assertThat(jsonStr, hasJsonPath("$.content[1].value", is("2")));
            assertThat(jsonStr, hasJsonPath("$.content[2].value", is("3")));
            assertThat(jsonStr, hasJsonPath("$.totalPages", is(1)));
            assertThat(jsonStr, hasJsonPath("$.totalElements", is(3)));
            assertThat(jsonStr, hasJsonPath("$.last", is(true)));
            assertThat(jsonStr, hasJsonPath("$.size", is(10)));
            assertThat(jsonStr, hasJsonPath("$.number", is(0)));
            assertThat(jsonStr, hasJsonPath("$.numberOfElements", is(3)));
            assertThat(jsonStr, hasJsonPath("$.first", is(true)));
            assertThat(jsonStr, hasJsonPath("$.empty", is(false)));
        }
    }

    @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
    @RequiredArgsConstructor(staticName = "of")
    @JsonView(Views.Base.class)
    @Getter
    public static class Obj {
        String value;
    }

    private Page<Obj> generateTestData() {
        return new PageImpl<>(
                Arrays.asList(Obj.of("1"), Obj.of("2"), Obj.of("3")),
                PageRequest.of(0, 10), 3);
    }
}

All the tests written above work.

But MockMVC tests and testing manually by cURLing my api do not work. Here is an example on how I test it:

@SpringBootTest
@AutoConfigureMockMvc
class ApiIntegrationTest {
        @Autowired MockMvc mockMvc;

        @Test
        void testDefaultBehaviour() throws Exception {
            mockMvc.perform(get("/api/v1/resources"))
                    .andExpect(status().isOk())
                    .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
                    .andExpect(contentIsValidPagedResponseOfResources());
        }

        private ResultMatcher contentIsValidPagedResponseOfCredentials() {
            return ResultMatcher.matchAll(
                    jsonPath("$.content[*].value").value(Every.everyItem(Is.isA(String.class))));
        }
}

This last tests fails. And the body is an empty json object {}, response code is 200. It fails on the jsonPath conditions.

Comment From: le-doude

Was this the right place for this issue?

Comment From: rstoyanchev

Can you provide access to the source for the above reproducer?

Comment From: spring-projects-issues

If you would like us to look at this issue, please provide the requested information. If the information is not provided within the next 7 days this issue will be closed.

Comment From: le-doude

@rstoyanchev @spring-projects-issues I am closing this because after trying to reproduce again it seems to work in a "clean" project. So I will investigate my own real project for anything interferring. https://github.com/le-doude/spring-jpa-paging-jackson-view

Sorry for wasting your time.