Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 72 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -354,17 +354,18 @@ And for the page size are:
- **page[size]**
- **page[limit]**

#### Request page always starts from 0 for compatible with spring repositories and etc.!
#### If request page number < 1 resolver always return number = 0 and if size < 1 it always returns default value = 25!
#### Request page always starts from 0 for compatibility with spring repositories and etc.!
#### If request page number < 1, resolver always return number = 0, and if size < 1, it always returns default value of 10!
#### Those default values can be overridden by annotating the method parameter with ```@PageableDefault```

### Sorting

See documentation part: [fetching-sorting](https://jsonapi.org/format/#fetching-sorting)

If you want to use request sort with annotation ```@RequestJsonApiPage``` add argument resolver in your configuration
If you want to use request sort with annotation ```@RequestJsonApiPage```, add the argument resolver in your configuration
as described in section [Pagination](#pagination).

Then you can use annotation ```@RequestJsonApiPage``` in controllers and get standard spring Pageable object.
Then you can use the annotation ```@RequestJsonApiPage``` in controllers and get a standard spring Pageable object.

For example:
```java
Expand All @@ -384,12 +385,78 @@ public class RestController {
}
```

Now if request will be contained list of fields like ```sort=field1,field2,-field3``` we can get them in the ```Pageable``` object.
Now if the request contains a list of fields like ```sort=field1,field2,-field3``` we can get them in the ```Pageable``` object.

For ASC order we just put fields name as is, for example: ```sort=name,age,...``` (see JSON:API spec).

For DESC order we should put fields name with prefix **-**, for example: ```sort=-name,age,...``` (see JSON:API spec).

To provide a default sort in case the request doesn't specify one, you can use either the one of the ```@SortDefault``` or
```@SortDefaults``` annotations, or the property **sort** and **direction** of the ```@PageableDefault```
annotation. See the examples bellow:

```java
@Slf4j
@RestController
@AllArgsConstructor
@RequestMapping(value = "/app", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public class RestController {
@GetMapping
public Response<Void> get(
@sortDefault(sort={"name", "age"}, direction=Sort.Direction.ASC)
final @RequestJsonApiPage Pageable page) throws Exception {
final Sort sort = page.getSort();
// do anything with sort

return Response.<Void, Void>builder()
.build();
}
}
```

```java
@Slf4j
@RestController
@AllArgsConstructor
@RequestMapping(value = "/app", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public class RestController {
@GetMapping
public Response<Void> get(
@SortDefault.SortDefaults({
@SortDefault(sort={"name", "age"}, direction=Sort.Direction.ASC),
@SortDefault(sort={"updateAt"}, direction=Sort.Direction.DESC)
})
final @RequestJsonApiPage Pageable page) throws Exception {
final Sort sort = page.getSort();
// do anything with sort

return Response.<Void, Void>builder()
.build();
}
}
```

```java
@Slf4j
@RestController
@AllArgsConstructor
@RequestMapping(value = "/app", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public class RestController {
@GetMapping
public Response<Void> get(
@PageableDefault(
sort={"name", "age"}, direction=Sort.Direction.ASC
)
final @RequestJsonApiPage Pageable page) throws Exception {
final Sort sort = page.getSort();
// do anything with sort

return Response.<Void, Void>builder()
.build();
}
}
```

### Other response examples
Example response with one data object:
```java
Expand Down
Original file line number Diff line number Diff line change
@@ -1,47 +1,83 @@
package com.slmdev.jsonapi.simple.resolver;

import com.slmdev.jsonapi.simple.annotation.RequestJsonApiPage;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.springframework.core.MethodParameter;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PageableArgumentResolver;
import org.springframework.data.web.PageableDefault;
import org.springframework.data.web.SortDefault;
import org.springframework.data.web.SortDefault.SortDefaults;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.slmdev.jsonapi.simple.annotation.RequestJsonApiPage;

/**
* Spring resolver using for extract page values from the request.
*
* <p>By default using {@code page} param name and key names in square brackets,
* for example {@code page[number]=3&page[size]=15}.
*
* After parsing will be created new spring {@link org.springframework.data.domain.Pageable} object.
* Optionally, if provided with a {@link org.springframework.data.web.PageableDefault}, it uses those
* values as the defaults when parsing the request parameters.
*
* After the parsing, a new spring {@link org.springframework.data.domain.Pageable} object will be created.
*
* <p>This resolver must be registered in Spring application.
*/
public class JsonApiPageArgumentResolver implements HandlerMethodArgumentResolver {
public class JsonApiPageArgumentResolver implements PageableArgumentResolver {
private static final String REQUEST_PAGE_KEY_BRACKET_START = "[";
private static final String REQUEST_PAGE_KEY_BRACKET_END = "]";

public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterAnnotation(RequestJsonApiPage.class) != null;
return Pageable.class.equals(parameter.getParameterType()) &&
parameter.getParameterAnnotation(RequestJsonApiPage.class) != null;
}

@NonNull
@Override
public Pageable resolveArgument(final MethodParameter methodParameter,
final ModelAndViewContainer modelAndViewContainer,
final NativeWebRequest nativeWebRequest,
final WebDataBinderFactory webDataBinderFactory) {
final RequestJsonApiPage RequestJsonApiPage = methodParameter.getParameterAnnotation(RequestJsonApiPage.class);
final String pageKeyStart = RequestJsonApiPage.name() + REQUEST_PAGE_KEY_BRACKET_START;
final Sort sort = parseSortField(nativeWebRequest);
int page = 0;
int size = 25;

final SortDefault sortDefault = methodParameter.getParameterAnnotation(SortDefault.class);
final SortDefaults sortDefaults = methodParameter.getParameterAnnotation(SortDefaults.class);
final PageableDefault pageableDefault = methodParameter.getParameterAnnotation(PageableDefault.class);

if (sortDefault != null && sortDefaults != null) {
throw new IllegalArgumentException(
String.format(
"Cannot use both @%s and @%s on parameter %s; Move %s into %s to define sorting order",
SortDefaults.class.getSimpleName(),
SortDefault.class.getSimpleName(),
methodParameter.toString(),
SortDefault.class.getSimpleName(),
SortDefaults.class.getSimpleName()
)
);
}

final RequestJsonApiPage requestJsonApiPage = methodParameter.getParameterAnnotation(RequestJsonApiPage.class);

final String pageKeyStart = requestJsonApiPage.name() + REQUEST_PAGE_KEY_BRACKET_START;

final Sort sort = parseSortField(nativeWebRequest, sortDefault, sortDefaults, pageableDefault);
int page = pageableDefault == null ? 0 : pageableDefault.page();
int size = pageableDefault == null ? 10 : pageableDefault.size();

final List<Map.Entry<String, String[]>> entries = nativeWebRequest.getParameterMap()
.entrySet()
Expand Down Expand Up @@ -101,10 +137,42 @@ private List<String> valueToList(final String[] values) {
return valueItems;
}

private Sort parseSortField(final NativeWebRequest nativeWebRequest) {
@Nullable
private Sort parseSortField(
final NativeWebRequest nativeWebRequest,
@Nullable final SortDefault sortDefault,
@Nullable final SortDefaults sortDefaults,
@Nullable final PageableDefault pageableDefault
) {

if (CollectionUtils.isEmpty(nativeWebRequest.getParameterMap())
|| nativeWebRequest.getParameterMap().get("sort") == null) {

SortDefault[] sortDefaultArray =
sortDefaults != null ?
sortDefaults.value() :
sortDefault != null ?
new SortDefault[]{sortDefault} :
new SortDefault[0];

// Takes the sort from sortDefalt(s)
if (sortDefaultArray.length > 0) {
return Arrays.stream(sortDefaultArray).map(
currentSortDefault -> Sort.by(
currentSortDefault.direction(),
currentSortDefault.sort()
)
).reduce(
Sort.unsorted(),
Sort::and
);
// Takes the sort from pageableDefault
} else if (pageableDefault != null && pageableDefault.sort().length > 0) {
return Sort.by(pageableDefault.direction(), pageableDefault.sort());
}

return null;

}
final var sortOrders = Arrays.stream(nativeWebRequest.getParameterMap()
.get("sort"))
Expand Down
Loading