27

Trying to unit test receiving a page from spring boot. If used with javascript the page can be easily be deserialized, but with java, it fails. Have added a default constructor for spring (which is an accepted answer in another stackoverflow post) but it does not work here.

Unit Test

@Test
public void test_read_pagination_happy(@Autowired ObservationSet set) {

    repository.save(set);

    final HttpEntity<String> authHeaders = authentication.convert("", authSuccess);
    final ParameterizedTypeReference<RestResponsePage<ObservationSet>> responseType = new ParameterizedTypeReference<RestResponsePage<ObservationSet>>() {
    };
    // final ResponseEntity<String> result = restTemplate.exchange(base + "/api/v1/observationset", HttpMethod.GET, authHeaders, String.class);
    final ResponseEntity<RestResponsePage<ObservationSet>> result = restTemplate.exchange(base + "/api/v1/observationset", HttpMethod.GET, authHeaders,
                    responseType);
    System.out.println(result.getBody());
    assertSame(HttpStatus.OK, result.getStatusCode(), "incorrect status code");
}

RestRespongePage class

class RestResponsePage<T> extends PageImpl<T> {

    private static final long serialVersionUID = 3248189030448292002L;

    public RestResponsePage(List<T> content, Pageable pageable, long total) {
        super(content, pageable, total);
    }

    public RestResponsePage(List<T> content) {
        super(content);
    }

    public RestResponsePage() {
        super(new ArrayList<T>());
    }

}

```

Code is available on github --> https://github.com/OpenPCM/openpcm-server/blob/integration-test/src/test/java/org/openpcm/controller/ObservationSetControllerIntTest.java

The deserialization throws this error:

org.springframework.http.converter.HttpMessageConversionException: Type definition error: [simple type, class org.springframework.data.domain.Pageable]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `org.springframework.data.domain.Pageable` (no Creators, like default construct, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
 at [Source: (PushbackInputStream); line: 1, column: 294] (through reference chain: org.openpcm.controller.RestResponsePage["pageable"])
    at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:240)
    at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.read(AbstractJackson2HttpMessageConverter.java:225)
    at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:100)
    at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:959)
    at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:942)
    at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:689)
    at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:644)
    at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:593)
    at org.springframework.boot.test.web.client.TestRestTemplate.exchange(TestRestTemplate.java:843)
    at org.openpcm.controller.ObservationSetControllerIntTest.test_read_pagination_happy(ObservationSetControllerIntTest.java:85)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:564)
    at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:436)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:115)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:170)
    at org.junit.jupiter.engine.execution.ThrowableCollector.execute(ThrowableCollector.java:40)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:166)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:113)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:58)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.lambda$executeRecursively$3(HierarchicalTestExecutor.java:112)
    at org.junit.platform.engine.support.hierarchical.SingleTestExecutor.executeSafely(SingleTestExecutor.java:66)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.executeRecursively(HierarchicalTestExecutor.java:108)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.execute(HierarchicalTestExecutor.java:79)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.lambda$executeRecursively$2(HierarchicalTestExecutor.java:120)
    at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184)
    at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:177)
    at java.base/java.util.Iterator.forEachRemaining(Iterator.java:133)
    at java.base/java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1801)
    at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)
    at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
    at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151)
    at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174)
    at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
    at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:430)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.lambda$executeRecursively$3(HierarchicalTestExecutor.java:120)
    at org.junit.platform.engine.support.hierarchical.SingleTestExecutor.executeSafely(SingleTestExecutor.java:66)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.executeRecursively(HierarchicalTestExecutor.java:108)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.execute(HierarchicalTestExecutor.java:79)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.lambda$executeRecursively$2(HierarchicalTestExecutor.java:120)
    at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184)
    at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:177)
    at java.base/java.util.Iterator.forEachRemaining(Iterator.java:133)
    at java.base/java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1801)
    at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)
    at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
    at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151)
    at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174)
    at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
    at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:430)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.lambda$executeRecursively$3(HierarchicalTestExecutor.java:120)
    at org.junit.platform.engine.support.hierarchical.SingleTestExecutor.executeSafely(SingleTestExecutor.java:66)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.executeRecursively(HierarchicalTestExecutor.java:108)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.execute(HierarchicalTestExecutor.java:79)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:55)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:43)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:170)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:154)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:90)
    at org.eclipse.jdt.internal.junit5.runner.JUnit5TestReference.run(JUnit5TestReference.java:89)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:41)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:541)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:763)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:463)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:209)
Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `org.springframework.data.domain.Pageable` (no Creators, like default construct, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
 at [Source: (PushbackInputStream); line: 1, column: 294] (through reference chain: org.openpcm.controller.RestResponsePage["pageable"])
    at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67)
    at com.fasterxml.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1451)
    at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1027)
    at com.fasterxml.jackson.databind.deser.AbstractDeserializer.deserialize(AbstractDeserializer.java:265)
    at com.fasterxml.jackson.databind.deser.impl.FieldProperty.deserializeAndSet(FieldProperty.java:136)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:288)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:151)
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4013)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3084)
    at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:237)
    ... 65 more
Mikhail Kopylov
  • 2,008
  • 5
  • 27
  • 58
GSUgambit
  • 4,459
  • 6
  • 25
  • 31
  • 1
    Please provide the text for the exception. (Please refer: http://idownvotedbecau.se/imageofanexception/) – Prashant Sep 25 '18 at 05:25
  • A workaround would be not exposing the `Page` as the response type from the controller endpoint and returning a List instead. The `Page` has a method `getContent()` that you can invoke for the conversion from Page to List. As I said, not a fix but a workaround. –  Sep 25 '18 at 08:20
  • @alexrolea i actually want to return the page though not just the list so if you are in a web client you can tab thru pages – GSUgambit Sep 26 '18 at 02:47
  • @Prashant have removed the picture and added the text – GSUgambit Sep 26 '18 at 02:54
  • @GSUgambit if the client tracks something like a `pageIndex` and `pageSize` on his side and sends the two parameters with the request you don't really need to expose the `Page` to the client. But, as I said, this is more of a workaround and less of a fix :) –  Sep 26 '18 at 03:14
  • @alexrolea if you don't send back the page, the client doesnt know the number of pages etc – GSUgambit Sep 26 '18 at 03:53
  • @GSUgambit if you have a non-GUI client you are right. Most GUI clients track that information (`pageIndex` and `pageSize`) by the table state in which you would display `ObservationSet`. There is usually a dropdown with `items per page`, with predefined sizes (this maps to the `pageSize`) and buttons with some state (`page 0`, `page1`, `next`, `previous`, `first`, `last`) which map to the `pageIndex`. The index button states are computed by the GUI, by submitting a count query first in order to determine how many pages are there (so you can go to the last page without iterating everything). –  Sep 26 '18 at 04:01
  • 1
    yeah but if you hide the page objects "numberOfPages" etc from the response the UI would not know how many possible pages there are. You could hard code how many elements you want to show per page but you could not know total page numbers which wouldn't allow you to show page1...page127 etc., you would basically only know the page numbers that exist after you make the calls and get back a different number of elements I found the answer on how to make this work though now – GSUgambit Sep 26 '18 at 04:18
  • Possible duplicate of [Spring RestTemplate with paginated API](https://stackoverflow.com/questions/34647303/spring-resttemplate-with-paginated-api) – Loren Jan 29 '19 at 14:49
  • Does this answer your question? [How to consume Page response using Spring RestTemplate](https://stackoverflow.com/questions/34099559/how-to-consume-pageentity-response-using-spring-resttemplate) – Aekansh Kansal Jul 21 '21 at 23:23

11 Answers11

51

Found the answer in the different post on here. It was not the accepted answer which it should be

Spring RestTemplate with paginated API

the answer by @rvheddeg is correct. You just need to put @JsonCreator and provide a constructor with all the properties, here is my RestResponsePage class that fixes the issue.

class RestResponsePage<T> extends PageImpl<T> {

    private static final long serialVersionUID = 3248189030448292002L;

    @JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
    public RestResponsePage(@JsonProperty("content") List<T> content, @JsonProperty("number") int number, @JsonProperty("size") int size,
                    @JsonProperty("totalElements") Long totalElements, @JsonProperty("pageable") JsonNode pageable, @JsonProperty("last") boolean last,
                    @JsonProperty("totalPages") int totalPages, @JsonProperty("sort") JsonNode sort, @JsonProperty("first") boolean first,
                    @JsonProperty("numberOfElements") int numberOfElements) {
        super(content, PageRequest.of(number, size), totalElements);
    }

    public RestResponsePage(List<T> content, Pageable pageable, long total) {
        super(content, pageable, total);
    }

    public RestResponsePage(List<T> content) {
        super(content);
    }

    public RestResponsePage() {
        super(new ArrayList<T>());
    }

}
GSUgambit
  • 4,459
  • 6
  • 25
  • 31
14

There's a simplier way to do it - to create and register custom deserializator for Page interface. So the usage will be straightforward:

//Catalog is the paged entity
Page<Catalog> page = objectMapper.readValue(content, new TypeReference<Page<Catalog>>() {});

ObjectMapper configuration:

ObjectMapper objectMapper= new ObjectMapper();
objectMapper.registerModule(new PageModule());

PageModule:

public class PageModule extends SimpleModule {
    private static final long serialVersionUID = 1L;

    public PageModule() {
        addDeserializer(Page.class, new PageDeserializer());
    }
}

PageDeserializer:

public class PageDeserializer extends JsonDeserializer<Page<?>> implements ContextualDeserializer {
    private static final String CONTENT = "content";
    private static final String NUMBER = "number";
    private static final String SIZE = "size";
    private static final String TOTAL_ELEMENTS = "totalElements";
    private JavaType valueType;

    @Override
    public Page<?> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
        final CollectionType valuesListType = ctxt.getTypeFactory().constructCollectionType(List.class, valueType);

        List<?> list = new ArrayList<>();
        int pageNumber = 0;
        int pageSize = 0;
        long total = 0;
        if (p.isExpectedStartObjectToken()) {
            p.nextToken();
            if (p.hasTokenId(JsonTokenId.ID_FIELD_NAME)) {
                String propName = p.getCurrentName();
                do {
                    p.nextToken();
                    switch (propName) {
                        case CONTENT:
                            list = ctxt.readValue(p, valuesListType);
                            break;
                        case NUMBER:
                            pageNumber = ctxt.readValue(p, Integer.class);
                            break;
                        case SIZE:
                            pageSize = ctxt.readValue(p, Integer.class);
                            break;
                        case TOTAL_ELEMENTS:
                            total = ctxt.readValue(p, Long.class);
                            break;
                        default:
                            p.skipChildren();
                            break;
                    }
                } while (((propName = p.nextFieldName())) != null);
            } else {
                ctxt.handleUnexpectedToken(handledType(), p);
            }
        } else {
            ctxt.handleUnexpectedToken(handledType(), p);
        }

        //Note that Sort field of Page is ignored here.
        //Feel free to add more switch cases above to deserialize it as well.  
        return new PageImpl<>(list, PageRequest.of(pageNumber, pageSize), total);
    }

    /**
     * This is the main point here.
     * The PageDeserializer is created for each specific deserialization with concrete generic parameter type of Page.
     */
    @Override
    public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) {
        //This is the Page actually
        final JavaType wrapperType = ctxt.getContextualType();
        final PageDeserializer deserializer = new PageDeserializer();
        //This is the parameter of Page
        deserializer.valueType = wrapperType.containedType(0);
        return deserializer;
    }
}
Mikhail Kopylov
  • 2,008
  • 5
  • 27
  • 58
  • 2
    This is as good as the accepted answer, some other alternatives may cause the Blockhound to report a blocking call when deserializing a Page. I'm using Spring WebFlux. – LeoFuso Jul 22 '21 at 21:57
14

This worked (org.springframework.cloud:spring-cloud-openfeign-core:2.2.5.RELEASE):

@Configuration
public class FeignConfigurationFactory {

    @Bean
    public Module pageJacksonModule() {
        return new PageJacksonModule();
    }

    @Bean
    public Module sortJacksonModule() {
        return new SortJacksonModule();
    }
}
Marcio Esposito
  • 141
  • 1
  • 2
  • 2
    I got `java.lang.ClassNotFoundException: feign.codec.EncodeException` with `org.springframework.cloud:spring-cloud-openfeign-core` when run as jar. need use `org.springframework.cloud:spring-cloud-starter-openfeign` instead. – min Dec 25 '20 at 09:37
8

Had to make a small change to ignore the unknown property of empty:

package ...helper;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.JsonNode;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;

import java.util.ArrayList;
import java.util.List;

@JsonIgnoreProperties(ignoreUnknown = true)
public class RestResponsePage<T> extends PageImpl<T> {
    @JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
    public RestResponsePage(@JsonProperty("content") List<T> content,
                            @JsonProperty("number") int number,
                            @JsonProperty("size") int size,
                            @JsonProperty("totalElements") Long totalElements,
                            @JsonProperty("pageable") JsonNode pageable,
                            @JsonProperty("last") boolean last,
                            @JsonProperty("totalPages") int totalPages,
                            @JsonProperty("sort") JsonNode sort,
                            @JsonProperty("first") boolean first,
                            @JsonProperty("numberOfElements") int numberOfElements) {

        super(content, PageRequest.of(number, size), totalElements);
    }

    public RestResponsePage(List<T> content, Pageable pageable, long total) {
        super(content, pageable, total);
    }

    public RestResponsePage(List<T> content) {
        super(content);
    }

    public RestResponsePage() {
        super(new ArrayList<>());
    }
}
Loren
  • 9,783
  • 4
  • 39
  • 49
4

These solutions don't work in tests universe if you are using response and serializing it. I had to serialize the content in List myself :)
In this scenario i am using RestAssured response.

protected static <T> List<T> getResponsePageDTO(Response response, Class<T> clazz) throws IOException {
    Map<String, Object> objectMap = objectMapper.readValue(response.getBody().print(), Map.class);
    List<Object> content = (List<Object>) objectMap.get("content");
    List<T> result = new ArrayList<>();
    for (Object o : content) {
        result.add(objectMapper.convertValue(o, clazz));
    }
    return result;
}
Gaspar
  • 1,515
  • 13
  • 20
3

Similar to @GSUgambit answer but more compact, since you do not have to specify the unused constructor parameters. The trick is to use @JsonIgnoreProperties.

@JsonIgnoreProperties(ignoreUnknown = true, value = {"pageable"})
public class RestPage<T> extends PageImpl<T> {
    @JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
    public RestPage(@JsonProperty("content") List<T> content,
                    @JsonProperty("number") int page,
                    @JsonProperty("size") int size,
                    @JsonProperty("totalElements") long total) {
        super(content, PageRequest.of(page, size), total);
    }

    public RestPage(Page<T> page) {
        super(page.getContent(), page.getPageable(), page.getTotalElements());
    }
}
attalos
  • 454
  • 5
  • 9
1

In fact, spring mvc serialize object by jackson, so inject a pageImp serializer should resolve the problem.

step 1: PageSerializer class :

   public class PageSerializer extends StdSerializer<PageImpl> {

    public PageSerializer() {
        super(PageImpl.class);
    }

    @Override
    public void serialize(PageImpl value, JsonGenerator gen, SerializerProvider provider) throws IOException {
        gen.writeStartObject();
        gen.writeNumberField("number", value.getNumber());
        gen.writeNumberField("numberOfElements", value.getNumberOfElements());
        gen.writeNumberField("totalElements", value.getTotalElements());
        gen.writeNumberField("totalPages", value.getTotalPages());
        gen.writeNumberField("size", value.getSize());
        gen.writeFieldName("content");
        provider.defaultSerializeValue(value.getContent(), gen);
        gen.writeEndObject();
    }
 }

step 2: inject jackson Moudle:

@Bean
public Module jacksonPageWithJsonViewModule() {
    SimpleModule module = new SimpleModule("jackson-page-with-jsonview",
        unknownVersion());
    module.addSerializer(PageImpl.class, new PageSerializer());
    return module;
}

ok, end.

steven
  • 377
  • 3
  • 14
1

There is a PageJacksonModule and a SortJacksonModule.

Kotlin:

private val mapper = jacksonObjectMapper().registerModules(
    PageJacksonModule(),
    SortJacksonModule()
)
TheJeff
  • 3,665
  • 34
  • 52
0

You can do something like this

final PageImplDeserializer<YOUR_CLASS> response = objectMapper.readValue(jsonResponse, new TypeReference<>() {});


public class PageImplDeserializer<T> {
    private List<T> content;
    private int number;
    private int size;
    private Long totalElements;
    private JsonNode pageable;
    private boolean last;
    private int totalPages;
    private JsonNode sort;
    private boolean first;
    private int numberOfElements;

... HERE GETTERS AND SETTERS...

}
Andrea Ciccotta
  • 598
  • 6
  • 16
0

I have this problem on kotlin and solved on this way:

import com.fasterxml.jackson.annotation.JsonCreator
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.databind.JsonNode
import org.springframework.data.domain.PageImpl
import org.springframework.data.domain.PageRequest

class RestResponsePage<T> @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) constructor(
    @JsonProperty("content") content: List<T>?,
    @JsonProperty("number") number: Int,
    @JsonProperty("size") size: Int,
    @JsonProperty("totalElements") totalElements: Long?,
    @JsonProperty("pageable") pageable: JsonNode?,
    @JsonProperty("last") last: Boolean,
    @JsonProperty("totalPages") totalPages: Int,
    @JsonProperty("sort") sort: JsonNode?,
    @JsonProperty("first") first: Boolean,
    @JsonProperty("numberOfElements") numberOfElements: Int
) : PageImpl<T>(
    content!!, PageRequest.of(number, size),
    totalElements!!
)

And is used like this:

private fun getPersonsRequest(
        page: Int,
        size: Int,
    ): RestResponsePage<PersonsResponse> {}
-2

To retrieve objects from Page content, there is no need to code a specific mapper, use objectMapper and the class org.springframework.data.domain.PageImpl

Here is the method:

private <T> List<T> getResponsePageContent(ObjectMapper objectMapper, ResultActions result, Class<T> clazz) throws IOException {
   return  objectMapper.readValue(result.andReturn().getResponse().getContentAsString(), 
                             new TypeReference<PageImpl<T>>() {})
                            .getContent();
}

And I use it like this:

List<Book> books = getResponsePageContent(objectMapper, result, Book.class);
Raouf Makhlouf
  • 193
  • 1
  • 8