I'm trying to implement a simple paged API using Micronaut 3.7.9, with micronaut-serde-jackson and I've encountered an issue with how my response is serialized, as Page properties are not included. My controller method return type is io.micronaut.data.model.Page.
Instead of getting a response looking like this (example response generated with micronaut-openapi's swagger):
{
"content": [
{
"id": 0,
"name": "Group1"
}
],
"pageable": {
"orderBy": [
{
"ignoreCase": true,
"direction": "ASC",
"property": "string",
"ascending": true
}
],
"number": 0,
"size": 0,
"sort": {
"orderBy": [
{
"ignoreCase": true,
"direction": "ASC",
"property": "string",
"ascending": true
}
]
},
"sorted": true
},
"pageNumber": 0,
"offset": 0,
"size": 0,
"empty": true,
"numberOfElements": 0,
"totalSize": 0,
"totalPages": 0
}
I'm getting just the plain array of the items from pageable's content array:
[
{
"id": 0,
"name": "Group1"
}
]
I've traced this issue back to the io.micronaut.serde.support.DefaultSerdeRegistry's findSerializer method which returns a io.micronaut.serde.support.serializers.IterableSerializer for a Page instance (which is understandable as Page effectively implements Iterable).
I'm wondering if there is a way to configure Serde to serialize Pageable with its instance fields (size, totalPages etc.), other than creating a custom Page which does not inherit from Iterable or registering own Serializer instance especially for io.micronaut.data.model.Page?
[EDIT] Relevant classes:
Page:
Repository:
package org.example.repository;
import io.micronaut.data.annotation.Repository;
import io.micronaut.data.repository.PageableRepository;
import org.example.model.Group;
@Repository
public interface GroupRepository extends PageableRepository<Group, Long> {
}
Service:
package org.example.service;
import io.micronaut.data.model.Page;
import io.micronaut.data.model.Pageable;
import jakarta.inject.Singleton;
import lombok.RequiredArgsConstructor;
import org.example.dto.GroupDto;
import org.example.repository.GroupRepository;
@Singleton
@RequiredArgsConstructor
public class GroupService {
private final GroupRepository groupRepository;
public Page<GroupDto> findAllGroups(Pageable pageable) {
return groupRepository.findAll(pageable).map(GroupDto::from);
}
}
Controller:
package org.example.controller;
import io.micronaut.data.model.Page;
import io.micronaut.data.model.Pageable;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.swagger.v3.oas.annotations.Parameter;
import lombok.RequiredArgsConstructor;
import org.example.dto.GroupDto;
import org.example.service.GroupService;
@Controller("/groups")
@RequiredArgsConstructor
public class GroupController {
private final GroupService groupService;
@Get
public Page<GroupDto> findAllGroups(@Parameter(hidden = true) Pageable pageable) {
return groupService.findAllGroups(pageable);
}
}
DTO
package org.example.dto;
import io.micronaut.serde.annotation.Serdeable;
import org.example.model.Group;
@Serdeable
public record GroupDto(Long id, String name, int version) {
public static GroupDto from(Group group) {
return new GroupDto(group.getId(), group.getName(), group.getVersion());
}
}
Entity:
package org.example.model;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import javax.persistence.*;
@Entity
@Table(name = "_group")
@NoArgsConstructor @Getter @Setter
public class Group {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
private String name;
@Version
private int version;
}
You can use a custom serializer:
@Prototype
final class DefaultPageSerializer implements Serializer<DefaultPage<Object>> {
@Override
public void serialize(Encoder encoder, EncoderContext context, Argument<? extends DefaultPage<Object>> type, DefaultPage<Object> page) throws IOException {
Encoder e = encoder.encodeObject(type);
e.encodeKey("content");
Argument<List<Object>> contentType = Argument.listOf((Argument<Object>) type.getFirstTypeVariable().orElse(Argument.OBJECT_ARGUMENT));
context.findSerializer(contentType)
.createSpecific(context, contentType)
.serialize(e, context, contentType, page.getContent());
e.encodeKey("pageable");
Argument<Pageable> pageable = Argument.of(Pageable.class);
context.findSerializer(pageable)
.createSpecific(context, pageable)
.serialize(e, context, pageable, page.getPageable());
e.encodeKey("totalSize");
e.encodeLong(page.getTotalSize());
e.finishStructure();
}
}
This should be fixed in Micronaut 4.