Spring Data Pagination returns no results with JSONView

后端 未结 6 1531
广开言路
广开言路 2021-02-06 02:53

I am using Spring data pagination in my REST Controller and returning Paged entity. I would like to control the data returned as JSON with the help of JSONViews.

I am a

相关标签:
6条回答
  • 2021-02-06 03:26

    Try with below piece of code,

    @Configuration
    public class MyInterceptorConfig extends WebMvcConfigurerAdapter{
    
        @Override
        public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
          MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
          ObjectMapper mapper = new ObjectMapper() {
            private static final long serialVersionUID = 1L;
            @Override
            protected DefaultSerializerProvider _serializerProvider(SerializationConfig config) {
              // replace the configuration with my modified configuration.
              // calling "withView" should keep previous config and just add my changes.
              return super._serializerProvider(config.withView(TravelRequestView.MyRequests.class));
            }        
          };
          mapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, true);
          converter.setObjectMapper(mapper);
          converters.add(converter);
        }
    

    Although I don't want to take credit for this, It was a reference from

    Jackson JsonView not being applied

    It would retrieve all the variables of an entity which are annotated with jsonview (TravelRequestView.MyRequests.class) along with all the variables which are not annotated with jsonview. If you don't want certain properties of an object, annotated with different view.

    0 讨论(0)
  • 2021-02-06 03:31

    I actually found a simpler and better way of doing this. The problem I had was with the fact that I cannot set @JsonView annotations on the Page object I was receiving. So I created an implementation of the Page interface and added my JsonViews to it. And instead of returning Page, I am now returning MyPage

    public class MyPage<T> implements Page<T> {
    
        private Page<T> pageObj;
    
        public MyPage(Page<T> pageObj) {
            this.pageObj = pageObj;
        }
    
        @JsonView(PaginatedResult.class)
        @Override
        public int getNumber() {
            return pageObj.getNumber();
        }
    
        @Override
        public int getSize() {
            return pageObj.getSize();
        }
    
        @JsonView(PaginatedResult.class)
        @Override
        public int getNumberOfElements() {
            return pageObj.getNumberOfElements();
        }
    
        @JsonView(PaginatedResult.class)
        @Override
        public List<T> getContent() {
            return pageObj.getContent();
        }
    
        @Override
        public boolean hasContent() {
            return pageObj.hasContent();
        }
    
        @Override
        public Sort getSort() {
            return pageObj.getSort();
        }
    
        @JsonView(PaginatedResult.class)
        @Override
        public boolean isFirst() {
            return pageObj.isFirst();
        }
    
        @JsonView(PaginatedResult.class)
        @Override
        public boolean isLast() {
            return pageObj.isLast();
        }
    
        @Override
        public boolean hasNext() {
            return pageObj.hasNext();
        }
    
        @Override
        public boolean hasPrevious() {
            return pageObj.hasPrevious();
        }
    
        @Override
        public Pageable nextPageable() {
            return pageObj.nextPageable();
        }
    
        @Override
        public Pageable previousPageable() {
            return pageObj.previousPageable();
        }
    
        @Override
        public Iterator<T> iterator() {
            return pageObj.iterator();
        }
    
        @JsonView(PaginatedResult.class)
        @Override
        public int getTotalPages() {
            return pageObj.getTotalPages();
        }
    
        @JsonView(PaginatedResult.class)
        @Override
        public long getTotalElements() {
            return pageObj.getTotalElements();
        }
    
    }
    
    0 讨论(0)
  • 2021-02-06 03:32

    Setting DEFAULT_VIEW_INCLUSION has a global effect, while all we need is to be able to serialize a Page object. The following code will register a serializer for Page, and is a simple change to your code:

    @Bean
    public Module springDataPageModule() {
        return new SimpleModule().addSerializer(Page.class, new JsonSerializer<Page>() {
            @Override
            public void serialize(Page value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
                gen.writeStartObject();
                gen.writeNumberField("totalElements",value.getTotalElements());
                gen.writeNumberField("totalPages", value.getTotalPages());
                gen.writeNumberField("number", value.getNumber());
                gen.writeNumberField("size", value.getSize());
                gen.writeBooleanField("first", value.isFirst());
                gen.writeBooleanField("last", value.isLast());
                gen.writeFieldName("content");
                serializers.defaultSerializeValue(value.getContent(),gen);
                gen.writeEndObject();
            }
        });
    }
    

    Another (arguably more elegant) solution is to register the following ResponseBodyAdvice. It will make sure your REST endpoint will still return a JSON array, and set a HTTP header 'X-Has-Next-Page' to indicate whether there is more data. The advantages are: 1) No extra count(*) query to your DB (single query) 2) Response is more elegant, since it returns a JSON array

    /**
     * ResponseBodyAdvice to support Spring data Slice object in JSON responses.
     * If the value is a slice, we'll write the List as an array, and add a header to the HTTP response
     *
     * @author blagerweij
     */
    @ControllerAdvice
    public class SliceResponseBodyAdvice implements ResponseBodyAdvice<Object> {
    
        @Override
        public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
            return true;
        }
    
        @Override
        public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
            if (body instanceof Slice) {
                Slice slice = (Slice) body;
                response.getHeaders().add("X-Has-Next-Page", String.valueOf(slice.hasNext()));
                return slice.getContent();
            }
            return body;
        }
    }
    
    0 讨论(0)
  • 2021-02-06 03:49

    you need to add annotation @JsonView(TravelRequestView.MyRequests.class) recursively. Add it to the field you want to see in Page class.

    public class Page<T> {
        @JsonView(TravelRequestView.MyRequests.class)
        private T view;
     ...
    }
    

    or enable DEFAULT_VIEW_INCLUSION for ObjectMapper:

    <mvc:annotation-driven>
        <mvc:message-converters>
            <bean class="org.springframework.http.converter.StringHttpMessageConverter" />
            <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
                <property name="objectMapper">
                    <bean id="objectMapper" class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean">
                        <property name="defaultViewInclusion" value="true"/>
                    </bean>
                </property>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>
    

    or use dto objects for your responses where you can control all your views

    0 讨论(0)
  • 2021-02-06 03:50

    If you are using spring-boot, then another simpler solution would be add the following to application.yml

    spring:
      jackson:
        mapper:
          DEFAULT_VIEW_INCLUSION: true
    

    or application.properties

    spring.jackson.mapper.DEFAULT_VIEW_INCLUSION=true
    

    With this approach, we have the advantage of retaining the ObjectMapper managed by Spring Container and not creating a new ObjectMapper. So once we use the spring managed ObjectMapper, then any Custom Serialzers we define will still continue to work e.g CustomDateSerialzer

    Reference: http://docs.spring.io/spring-boot/docs/current/reference/html/howto-spring-mvc.html

    0 讨论(0)
  • 2021-02-06 03:52

    Still cleaner is to tell Jackson to Serialize all props for a bean of type Page. To do that, you just have to declare to adapt slighly the Jackson BeanSerializer : com.fasterxml.jackson.databind.ser.BeanSerializer Create a class that extends that BeanSerializer called PageSerialier and if the bean is of type Page<> DO NOT apply the property filtering.

    As show in the code below, i just removed the filtering for Page instances :

    public class MyPageSerializer extends BeanSerializer {
    
    /**
     * MODIFIED By Gauthier PEEL
     */
    @Override
    protected void serializeFields(Object bean, JsonGenerator gen, SerializerProvider provider)
            throws IOException, JsonGenerationException {
        final BeanPropertyWriter[] props;
        // ADDED
        // ADDED
        // ADDED
        if (bean instanceof Page) {
            // for Page DO NOT filter anything so that @JsonView is passthrough at this level
            props = _props;
        } else {
            // ADDED
            // ADDED
            if (_filteredProps != null && provider.getActiveView() != null) {
                props = _filteredProps;
            } else {
                props = _props;
            }
        }
    
            // rest of the method unchanged
        }
    
        // inherited constructor removed for concision
    }
    

    Then you need to declare it to Jackson with a Module :

    public class MyPageModule extends SimpleModule {
    
        @Override
        public void setupModule(SetupContext context) {
    
            context.addBeanSerializerModifier(new BeanSerializerModifier() {
    
                @Override
                public JsonSerializer<?> modifySerializer(SerializationConfig config, BeanDescription beanDesc,
                        JsonSerializer<?> serializer) {
                    if (serializer instanceof BeanSerializerBase) {
                        return new MyPageSerializer ((BeanSerializerBase) serializer);
                    }
                    return serializer;
    
                }
            });
        }
    }
    

    Spring conf now : in your @Configuration create a new @Bean of the MyPageModule

    @Configuration
    public class WebConfigPage extends WebMvcConfigurerAdapter {
    
        /**
         * To enable Jackson @JsonView to work with Page<T>
         */
        @Bean
        public MyPageModule myPageModule() {
            return new MyPageModule();
        }
    
    }
    

    And you are done.

    0 讨论(0)
提交回复
热议问题