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 cURL
ing 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.