spring-dataspring-configspring-data-commons

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
}

Solution

  • 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()));
                }
            }
        }
    }