如何使用restTemplate使用REST API来返回其中包含Page对象的对象?

问题描述 投票:0回答:3

API请求和响应:

要求:

http://localhost:5555/api/products?page=1&size=100

回复:

{
    "reports": {
        "content": [
            {
                "productCatTx": "ERROR_product",
                "productValTx": "000",
                "productShrtDescTx": "NO_ERROR",
                "productLongDescTx": "No Error in response"
            },
            {
                "productCatTx": "ERROR_product",
                "productValTx": "010",
                "productShrtDescTx": "PRODUCT_ACCEPTED",
                "productLongDescTx": "Product Accepted"
            }
        ],
        "pageable": {
            "sort": {
                "empty": false,
                "sorted": true,
                "unsorted": false
            },
            "offset": 0,
            "pageNumber": 0,
            "pageSize": 100,
            "unpaged": false,
            "paged": true
        },
        "totalElements": 321,
        "totalPages": 4,
        "last": false,
        "size": 100,
        "number": 0,
        "sort": {
            "empty": false,
            "sorted": true,
            "unsorted": false
        },
        "numberOfElements": 100,
        "first": true,
        "empty": false
    }
}

来自另一个项目的客户端 REST API 调用:

@GetMapping("/paginatedWResponseData")
    public void getProductsWResponseData() {
        RestTemplate restTemplate = new RestTemplate();
        String resourceUrl = "http://localhost:5555/api/products?page=1&size=100";

        UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUriString(resourceUrl)
                .queryParam("page", 1)
                .queryParam("size", 100);

        ResponseEntity<ReportResponse> responseEntity = restTemplate.exchange(uriBuilder.toUriString(),
                HttpMethod.GET, null, ReportResponse.class);

        System.out.println(responseEntity);
    }

ReportResponse.java

@Data
@Builder
@FieldDefaults(level = AccessLevel.PRIVATE)
public class ReportResponse extends CustomPageImpl implements Serializable {

    Page<Product> reportData;

    @JsonCreator
    public ReportResponse(@JsonProperty("reports") Page<Product> reportData) {
        super(reportData.getContent());
        this.reportData = reportData;
    }

}

CustomPageImpl.java

public class CustomPageImpl<T> extends PageImpl<T> {

    @JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
    public CustomPageImpl(@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("numberOfElements") int numberOfElements) {
        super(content, PageRequest.of(number, 1), 10);
    }

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

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

    public CustomPageImpl() {
        super(new ArrayList<>());
    }
}

错误:

2023-08-24T13:58:53.372-04:00 ERROR 20304 --- [nio-5656-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.springframework.http.converter.HttpMessageConversionException: Type definition error: [simple type, class org.springframework.data.domain.Page]] with root cause

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `org.springframework.data.domain.Page` (no Creators, like default constructor, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
 at [Source: (org.springframework.util.StreamUtils$NonClosingInputStream); line: 1, column: 12] (through reference chain: net.code.sample.ReportResponse["reports"])
java spring spring-boot spring-restcontroller spring-rest
3个回答
0
投票

您必须创建一个自定义反序列化器,然后注释 CustomPageImpl 类以告诉 jackson 使用自定义反序列化器。

自定义解串器

public class CustomPageDeserializer<T> extends StdDeserializer<CustomPageImpl<T>> {

    public CustomPageDeserializer() {
        this(null);
    }

    public CustomPageDeserializer(Class<?> vc) {
        super(vc);
    }

    @Override
    public CustomPageImpl<T> deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
        JsonNode node = jsonParser.getCodec().readTree(jsonParser);

        List<T> content = // Extract content from the node
        int number = // Extract number from the node
        int size = // Extract size from the node
        Long totalElements = // Extract totalElements from the node
        boolean last = // Extract last from the node
        int totalPages = // Extract totalPages from the node
        int numberOfElements = // Extract numberOfElements from the node

        Pageable pageable = PageRequest.of(number, size);

        return new CustomPageImpl<>(content, pageable, totalElements);
    }
}

如何注释

@JsonDeserialize(using = CustomPageDeserializer.class)
public class CustomPageImpl<T> extends PageImpl<T> {

0
投票

我在新的 Spring Boot 3.1.13 项目中尝试了这个并成功了:

package com.example.demo;

import java.util.HashMap;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Service;

import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonUnwrapped;
import com.fasterxml.jackson.databind.ObjectMapper;

@Service
public class Autoexec implements ApplicationRunner {

    private static final String TEST_JSON = """
    {
        "reports": {
            "content": [
                {
                    "productCatTx": "ERROR_product",
                    "productValTx": "000",
                    "productShrtDescTx": "NO_ERROR",
                    "productLongDescTx": "No Error in response"
                },
                {
                    "productCatTx": "ERROR_product",
                    "productValTx": "010",
                    "productShrtDescTx": "PRODUCT_ACCEPTED",
                    "productLongDescTx": "Product Accepted"
                }
            ],
            "pageable": {
                "sort": {
                    "empty": false,
                    "sorted": true,
                    "unsorted": false
                },
                "offset": 0,
                "pageNumber": 0,
                "pageSize": 100,
                "unpaged": false,
                "paged": true
            },
            "totalElements": 321,
            "totalPages": 4,
            "last": false,
            "size": 100,
            "number": 0,
            "sort": {
                "empty": false,
                "sorted": true,
                "unsorted": false
            },
            "numberOfElements": 100,
            "first": true,
            "empty": false
        }
    }
    """;

    private final ObjectMapper objectMapper;

    @Autowired
    public Autoexec(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }

    @Override
    public void run(ApplicationArguments args) throws Exception {
        final var response = objectMapper.readValue(TEST_JSON, ProductListResponse.class);
        System.err.println(response.getContent());
    }

    static class ProductListResponse extends Response<List<Product>> {
    }

    static class Product {

        public String productCatTx;
        public String productValTx;
        public String productShrtDescTx;
        public String productLongDescTx;
        
        @Override
        public String toString() {
            return "Product [productCatTx=" + productCatTx + ", productValTx=" + productValTx + ", productShrtDescTx="
                    + productShrtDescTx + ", productLongDescTx=" + productLongDescTx + "]";
        }

    }

    static class Response<T> {

        
        private Page<T> page;

        @JsonAnySetter
        public void setProperty(String name, Page<T> value) {
            page = value;
        }

        public T getContent() {
            return page == null ? null : page.content;
        }

    }

    static class Page<T> {

        public T content;

    }
}

您可以向页面类添加其他属性、将字段设为私有并添加 getter/setter 等。

结果:

[Product [productCatTx=ERROR_product, productValTx=000, productShrtDescTx=NO_ERROR, productLongDescTx=No Error in response], Product [productCatTx=ERROR_product, productValTx=010, productShrtDescTx=PRODUCT_ACCEPTED, productLongDescTx=Product Accepted]]

0
投票

堆栈跟踪告诉您 Jackson 库无法创建

org.springframework.data.domain.Page
实例,因为它没有构造函数,这是有道理的,因为它是一个接口。

我认为您真正需要的是调整

ReportResponse
以接收
CustomPageImpl
实例作为其构造函数中的参数。此外,您还可以像这样摆脱继承:

@Data
@Builder
@FieldDefaults(level = AccessLevel.PRIVATE)
public class ReportResponse implements Serializable {

    private final CustomPageImpl<Product> reportData;

    @JsonCreator
    public ReportResponse(@JsonProperty("reports") CustomPageImpl<Product> reportData) {
        this.reportData = reportData;
    }

    public CustomPageImpl<Product> getReportData() {
        return this.reportData;
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.