Skip to content

[Feature Request] Support custom DTO for spring.data.web.pageable.serialization-mode=via_dto #3433

@sanwenyukaochi

Description

@sanwenyukaochi

Motivation
Currently, spring.data.web.pageable.serialization-mode=via_dto uses the built-in PagedModel class
for JSON serialization. There is no way to provide a custom DTO globally, which limits projects
that need different field names or additional metadata like hasNext/hasPrevious.

Additionally, even when spring.data.web.pageable.one-indexed-parameters=true is set,
the returned number field in PagedModel still starts from 0 instead of 1.
It is unclear whether this is intentional or a bug. This makes it difficult to have
a consistent 1-based pagination model for both input and output without manually wrapping
the Page in a custom DTO in every controller.

Proposal
Add support for configuring a custom DTO class for serialization, for example:

spring.data.web.pageable.serialization-mode=via_dto
spring.data.web.pageable.dto-class=com.example.PagedModel
package com.example;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import org.jspecify.annotations.NonNull;
import org.springframework.data.domain.Page;
import org.springframework.util.Assert;

import java.util.List;

@JsonPropertyOrder({"content", "page"})
public record PagedModel<T>(Page<@NonNull T> page) {

    public PagedModel(Page<@NonNull T> page) {
        Assert.notNull(page, "Page must not be null");
        this.page = page;
    }

    @JsonProperty("content")
    public List<T> getContent() {
        return page.getContent();
    }

    @JsonProperty("page")
    public PageMetadata getMetadata() {
        return new PageMetadata(page.getSize(), page.getNumber() + 1, page.getTotalElements(),
                page.getTotalPages(),
                !page.isFirst(),
                !page.isLast());
    }

    public record PageMetadata(long size, long number, long totalElements, long totalPages, boolean hasNext, boolean hasPrevious) {

        public PageMetadata {
            Assert.isTrue(size > -1, "Size must not be negative!");
            Assert.isTrue(number > -1, "Number must not be negative!");
            Assert.isTrue(totalElements > -1, "Total elements must not be negative!");
            Assert.isTrue(totalPages > -1, "Total pages must not be negative!");
            Assert.notNull(hasNext, "hasNext must not be null");
            Assert.notNull(hasPrevious, "hasPrevious must not be null");
        }
    }

    public static <T> PagedModel<T> of(Page<@NonNull T> page) {
        Assert.notNull(page, "Page must not be null");
        return new PagedModel<>(page);
    }
}

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions