Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring Data Page doesn't serialize Sort to JSON correctly

This issue appeared in Spring-Data release 2. In latest version 1.13.9 (and older) it works fine.

Controller code:

@RestController
public class HelloController {

    @RequestMapping("/")
    public String index() {
        return "Greetings from Spring Boot!";
    }

    @RequestMapping(value = "sorttest", method = RequestMethod.GET)
    public Page<Integer> getDummy() {
        return new PageImpl<>(Collections.singletonList(1), new PageRequest(0, 5, new Sort("asdf")), 1);
    }

}

Same for Spring-Data 2 style:

Pageable pageable = PageRequest.of(0, 10, new Sort(Sort.Direction.ASC, "asd"));
PageImpl<Integer> page = new PageImpl<Integer>(Lists.newArrayList(1,2,3), pageable, 3);
return page;

Configuration:

@SpringBootApplication
@EnableWebMvc
@EnableSpringDataWebSupport
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

Also tried simple Spring application without Spring Boot with Java config as well as with XML config. Result is same:

{
    "content": [
        1
    ],
    "pageable": {
        "sort": {
            "sorted": true,
            "unsorted": false
        },
        "offset": 0,
        "pageSize": 5,
        "pageNumber": 0,
        "paged": true,
        "unpaged": false
    },
    "totalElements": 1,
    "last": true,
    "totalPages": 1,
    "size": 5,
    "number": 0,
    "sort": {
        "sorted": true,
        "unsorted": false
    },
    "numberOfElements": 1,
    "first": true
}

If I change Spring-Data version to 1.X I'm getting correct JSON response for sorting object:

{
    "content": [
        1
    ],
    "totalElements": 1,
    "totalPages": 1,
    "last": true,
    "size": 5,
    "number": 0,
    "sort": [
        {
            "direction": "ASC",
            "property": "asdf",
            "ignoreCase": false,
            "nullHandling": "NATIVE",
            "ascending": true,
            "descending": false
        }
    ],
    "numberOfElements": 1,
    "first": true
}

It seems I tried everything, I didn't find any notification on sort changes in changelog, I didn't find such issue in Spring JIRA.

So the question is how do I get with spring-data 2.X libs JSON with sorting like:

"sort": [
    {
        "direction": "ASC",
        "property": "asdf",
        "ignoreCase": false,
        "nullHandling": "NATIVE",
        "ascending": true,
        "descending": false
    }
]

instead of:

"sort": {
    "sorted": true,
    "unsorted": false
}
like image 417
Oleg Danyliuk Avatar asked Jan 15 '18 13:01

Oleg Danyliuk


People also ask

What is pagination in spring data JPA?

1. Overview Pagination is often helpful when we have a large dataset and we want to present it to the user in smaller chunks. Also, we often need to sort that data by some criteria while paging. In this tutorial, we'll learn how to easily paginate and sort using Spring Data JPA.

How to create a pagerequest object in Spring MVC?

We can create a PageRequest object by passing in the requested page number and the page size. In Spring MVC, we can also choose to obtain the Pageable instance in our controller using Spring Data Web Support.

How do I sort the fields in a pagerequest?

We can do that by passing the sorting details into our PageRequest object itself: Based on our sorting requirements, we can specify the sort fields and the sort direction while creating our PageRequest instance. As usual, we can then pass this Pageable type instance to the repository's method.


2 Answers

@Oleg Danyliuk

I have encountered the same issue as you do and found your response useful however here I provide a shortest answer.


As you said, it is needed to create a custom serializer for the Sort class.

But then, you only need to annotate the JsonSerializer class with @JsonComponent to register it with Jackson.


Solution

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import org.springframework.boot.jackson.JsonComponent;
import org.springframework.data.domain.Sort;

import java.io.IOException;

@JsonComponent
public class SortJsonSerializer extends JsonSerializer<Sort> {

    @Override
    public void serialize(Sort value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        gen.writeStartArray();

        value.iterator().forEachRemaining(v -> {
            try {
                gen.writeObject(v);
            } catch (IOException e) {
                e.printStackTrace();
            }
        });

        gen.writeEndArray();
    }

    @Override
    public Class<Sort> handledType() {
        return Sort.class;
    }
}

References

  • docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/jackson/JsonComponent.html
  • baeldung.com/spring-boot-jsoncomponent
like image 71
Alexis Lavie Avatar answered Sep 21 '22 14:09

Alexis Lavie


I've solved an issue adding my custom serializator:

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import org.springframework.data.domain.Sort;

import java.io.IOException;

public class SortJsonSerializer extends JsonSerializer<Sort> {

    @Override
    public void serialize(Sort orders, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        jsonGenerator.writeStartArray();
        orders.iterator().forEachRemaining(o -> {
            try {
                jsonGenerator.writeObject(o);
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
        jsonGenerator.writeEndArray();
    }

    @Override
    public Class<Sort> handledType() {
        return Sort.class;
    }

}

And to make Spring use it I override extendMessageConverters:

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = {"com.xxx.ws"})
@EnableSpringDataWebSupport
public class SpringWebConfig implements WebMvcConfigurer {

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        SortHandlerMethodArgumentResolver sortResolver = new SortHandlerMethodArgumentResolver();

        // For sorting resolution alone
        argumentResolvers.add(sortResolver);

        PageableHandlerMethodArgumentResolver pageableResolver = new PageableHandlerMethodArgumentResolver(sortResolver);
        pageableResolver.setMaxPageSize(10000);

        // For sorting resolution encapsulated inside a pageable
        argumentResolvers.add(pageableResolver);

        argumentResolvers.add(new CurrentUserArgumentResolver());
    }

    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }

    @Bean
    public Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder() {
        SimpleModule module = new SimpleModule();
        module.addSerializer(Sort.class, new SortJsonSerializer());
        return new Jackson2ObjectMapperBuilder()
                .findModulesViaServiceLoader(true)
                .modulesToInstall(module);
    }

    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        //First converters added in WebMvcConfigurationSupport.addDefaultHttpMessageConverters and then we add our behaviour here
        Jackson2ObjectMapperBuilder builder = jackson2ObjectMapperBuilder();

        for (int i=0; i<converters.size(); i++) {
            if (converters.get(i) instanceof MappingJackson2HttpMessageConverter) {
                converters.set(i, new MappingJackson2HttpMessageConverter(builder.build()));
            }
        }
    }
}
like image 30
Oleg Danyliuk Avatar answered Sep 19 '22 14:09

Oleg Danyliuk