使用Jackson自定义反序列化器,通过RestTemplate解析Spring-Boot 2.1.0页面响应

时间:2018-11-16 01:03:30

标签: java spring spring-boot jackson

请帮助我解析Spring响应。我正在尝试使用Jackson自定义解串器。 REST服务器在Spring-Boot 2.1.0.RELEASE上实现,它引入了Spring-Data-Commons 2.1.2和Jackson 2.9.7。服务器端点将输出的块作为Page 返回。在Spring-Boot版本1.5.x中工作的解串器现在抛出异常,因为2.1响应非常不同。这是来自spring-boot 2.1 REST端点的数据:

{
   "content": [ "3" ],
   "pageable": {
     "sort": {
       "sorted": false,
       "unsorted": true,
       "empty": true
     },
     "offset": 0,
     "pageSize": 20,
     "pageNumber": 0,
     "unpaged": false,
     "paged": true
  },
  "last": true,
  "totalPages": 1,
  "totalElements": 1,
  "size": 20,
  "number": 0,
  "numberOfElements": 1,
  "first": true,
  "sort": {
    "sorted": false,
    "unsorted": true,
    "empty": true
  },
  "empty": false
}

我尝试遵循这些SO答案的建议,只要我忽略“排序”字段即可使客户端正常工作。

com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of org.springframework.data.domain.Sort out of START_ARRAY token

Spring RestTemplate with paginated API

当我尝试解析排序字段时,下面的代码会产生异常:

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: 
Cannot construct instance of `org.mypackage.RestPageResponse`, 
problem: argument type mismatch at [Source: (String)
"{ "content":["1","2","3"],"first":true,"last":false,  "number":0,"numberOfElements":3,"size":3,"totalElements":90,"totalPages":30,  "sort":[{"direction":"ASC","property":"name","ignoreCase":false,"nullHandling":"NATIVE","ascending":true,"descending":false}]  }"; 
line: 1, column: 260]

下面是响应的Java类(域模型)。我已经将Sort数据建模为此类中的字段,这可能是错误的,应该有一种方法可以将Pageable对象中的数据传递给超类。

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

import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;

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

    private static final long serialVersionUID = 5835593096562217592L;

    private Sort sort;

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

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

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

    /*
     * https://stackoverflow.com/questions/34647303/spring-resttemplate-with-paginated-api
     */
    @JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
    public RestPageResponse(//
        @JsonProperty("content") List<T> content, // PageImpl
        @JsonProperty("number") int number, // PageImpl
        @JsonProperty("size") int size, // PageImpl
        @JsonProperty("totalElements") long totalElements, // PageImpl
        @JsonProperty("pageable") JsonNode pageable, //
        @JsonProperty("sort") JsonNode sort, //
        @JsonProperty("totalPages") int totalPages, // computed
        @JsonProperty("first") boolean first, // computed
        @JsonProperty("last") boolean last, // computed
        @JsonProperty("empty") boolean empty, // computed
        @JsonProperty("numberOfElements") int numberOfElements // computed
    ) {
    super(content, PageRequest.of(number, size), totalElements);
    }

    @Override
    public Sort getSort() {
        return sort;
    }

    @JsonDeserialize(using = CustomSortDeserializer.class)
    public void setSort(Sort sort) {
        this.sort = sort;
    }

}

这是自定义反序列化器,应归功于https://blog.thecookinkitchen.com/how-to-consume-page-response-from-a-service-in-spring-boot-97293c18ba

import java.io.IOException;

import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.domain.Sort.Order;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;

public class CustomSortDeserializer extends JsonDeserializer<Sort> {
    @Override
    public Sort deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
        ArrayNode node = jp.getCodec().readTree(jp);
        Order[] orders = new Order[node.size()];
        int i = 0;
        for (JsonNode obj : node) {
            orders[i] = new Order(Direction.valueOf(obj.get("direction").asText()), obj.get("property").asText());
            i++;
        }
        return Sort.by(orders);
    }
}

这是一个JUnit测试类,其中包含Spring-boot 1.5响应和2.1响应的用例,它们是完全不同的:

import java.lang.invoke.MethodHandles;

import org.acumos.cds.transport.RestPageResponse;
import org.junit.Assert;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

public class DeserializerTest {

    private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

    /*
     * Content emitted by Spring-Boot v1.5.x REST endpoint
     */
    @SuppressWarnings("rawtypes")
    @Test
    public void testRestPageResponse15() {
        String springBoot15 = "{" //
                + " \"content\":[\"1\",\"2\",\"3\"],\"first\":true,\"last\":false, " //
                + " \"number\":0,\"numberOfElements\":3,\"size\":3,\"totalElements\":90,\"totalPages\":30, " //
                + " \"sort\":[{\"direction\":\"ASC\",\"property\":\"name\",\"ignoreCase\":false,\"nullHandling\":\"NATIVE\",\"ascending\":true,\"descending\":false}] "//
                + " }";
        try {
            ObjectMapper mapper = new ObjectMapper();
            JsonNode sb15 = mapper.readTree(springBoot15);
            logger.info("SpringBoot 1.5 JSON {}", sb15.toString());
            RestPageResponse r = mapper.readValue(springBoot15, RestPageResponse.class);
            logger.info("Parsed JSON {}", mapper.writeValueAsString(r));
            Assert.assertNotNull(r.getContent());
            Assert.assertEquals(r.getContent().size(), 3);
            Assert.assertTrue(r.isFirst());
            Assert.assertFalse(r.isLast());
            Assert.assertEquals(r.getNumber(), 0);
            Assert.assertEquals(r.getNumberOfElements(), 3);
            Assert.assertEquals(r.getSize(), 3);
            Assert.assertEquals(r.getTotalPages(), 30);
            Assert.assertEquals(r.getTotalElements(), 90);
        } catch (Exception ex) {
            logger.error("testRestPageResponse15 failed", ex);
        }
    }

    /*
     * Content emitted by Spring-Boot v2.1.0 REST endpoint
     */
    @SuppressWarnings("rawtypes")
    @Test
    public void testRestPageResponse21() {
        String springBoot21 = "{"//
                + " \"content\":[\"4\",\"5\",\"6\"], \"empty\": false, "//
                + " \"pageable\": { \"sort\": {\"sorted\": false, \"unsorted\": true, \"empty\": true },"
                + " \"offset\": 0, \"pageSize\": 20, \"pageNumber\": 0, \"unpaged\": false, \"paged\": true },"
                + " \"last\": true, \"totalPages\": 1, \"totalElements\": 1, \"size\": 20, \"number\": 0, "
                + " \"numberOfElements\": 1,  \"first\": true, \"sort\": { \"sorted\": false, \"unsorted\": true, \"empty\": true }"
                + "}";
        try {
            ObjectMapper mapper = new ObjectMapper();
            JsonNode sb21 = mapper.readTree(springBoot21);
            logger.info("SpringBoot 2.1 JSON {}", sb21.toString());
            RestPageResponse s = mapper.readValue(springBoot21, RestPageResponse.class);
            logger.info("Parsed JSON {}", mapper.writeValueAsString(s));
            Assert.assertNotNull(s.getContent());
            Assert.assertEquals(s.getContent().size(), 3);
            Assert.assertTrue(s.isFirst());
            Assert.assertTrue(s.isLast());
            Assert.assertEquals(s.getNumber(), 0);
            Assert.assertEquals(s.getNumberOfElements(), 3);
            Assert.assertEquals(s.getSize(), 20);
            Assert.assertEquals(s.getTotalPages(), 1);
            Assert.assertEquals(s.getTotalElements(), 3);

        } catch (Exception ex) {
            logger.error("testRestPageResponse21 failed", ex);
        }
    }

}

如果我从响应模型中删除@JsonDeserialize批注,则解析工作正常,但“排序”字段的内容将被丢弃。

为什么这么难? https://jira.spring.io/browse/DATACMNS-1061的春季人们说“使用HATEOAS”,我根本没有想过。预先感谢。

1 个答案:

答案 0 :(得分:0)

这就是我进行分类工作的方式。

使用Rest模板使用API​​的类

package br.com.ironforge.springboottutorial.javaclient;

import java.util.Arrays;
import java.util.List;

import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;

import br.com.ironforge.springboottutorial.model.PageableResponse;
import br.com.ironforge.springboottutorial.model.Student;

public class JavaSpringClientTest {

    public static void main(String[] args) {

        RestTemplate restTemplate = new RestTemplateBuilder().rootUri("http://localhost:8080/v1/protected/students")
                .basicAuthentication("username", "password").build();

        ResponseEntity<PageableResponse<Student>> exchange = restTemplate.exchange("/?sort=id,asc", HttpMethod.GET, null,
                new ParameterizedTypeReference<PageableResponse<Student>>() {
                });
        System.out.println(exchange.getBody().getContent());
    }
}

用于分页和对API调用进行排序的类

package br.com.ironforge.springboottutorial.model;

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

import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.JsonNode;


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

    private boolean last;
    private boolean first;
    private int totalPages;

    @JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
    public PageableResponse(@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 PageableResponse(List<T> content, Pageable pageable, long total) {
        super(content, pageable, total);
    }

    public PageableResponse(List<T> content) {
        super(new ArrayList<T>());
    }

}