请帮助我解析Spring响应。我正在尝试使用Jackson自定义解串器。 REST服务器在Spring-Boot 2.1.0.RELEASE上实现,它引入了Spring-Data-Commons 2.1.2和Jackson 2.9.7。服务器端点将输出的块作为Page
{
"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答案的建议,只要我忽略“排序”字段即可使客户端正常工作。
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”,我根本没有想过。预先感谢。
答案 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>());
}
}