使用 Pageable 字段测试端点时出现 JsonMappingException
Posted
技术标签:
【中文标题】使用 Pageable 字段测试端点时出现 JsonMappingException【英文标题】:JsonMappingException when testing endpoints with Pageable field 【发布时间】:2018-11-22 09:01:59 【问题描述】:我需要调用一个需要Pageable
字段的端点:
@GetMapping
public Page<ProductDTO> listProducts(Pageable pageable)
return productService.findProducts(pageable);
在我的测试中,我有这个代码:
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
parameters.add("page", String.valueOf(0));
URI url = defaultURI(port, "/products", parameters);
ParameterizedTypeReference<RestResponsePage<ProductDTO>> type = new ParameterizedTypeReference<RestResponsePage<ProductDTO>>() ;
ResponseEntity<RestResponsePage<ProductDTO>> response = restTemplate.exchange(url.toString(), HttpMethod.GET, httpEntity, type);
PageImpl
不包含默认构造函数,因此为了避免这个问题,我创建了一个类似以下的类来传递给ParameterizedTypeReference
:
@JsonIgnoreProperties(ignoreUnknown = true) @Getter @Setter
public class RestResponsePage<T> extends PageImpl<T> implements Serializable
private static final long serialVersionUID = 3844794233375694591L;
@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
public RestResponsePage(@JsonProperty("content") List<T> content,
@JsonProperty("number") int page,
@JsonProperty("size") int size,
@JsonProperty("totalElements") long totalElements)
super(content, new PageRequest(page, size), totalElements);
public RestResponsePage(List<T> content, Pageable pageable, long totalElements)
super(content, pageable, totalElements);
public RestResponsePage(List<T> content)
super(content);
public RestResponsePage()
super(new ArrayList<T>());
问题是我仍然收到以下错误:
Caused by: com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of org.springframework.data.domain.Pageable: abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
at [Source: java.io.PushbackInputStream@75564689; line: 38, column: 16] (through reference chain: com.shaunyl.util.ResponsePageImpl["pageable"])
为什么它一直说我正在传递一个抽象类? ResponsePageImpl
是一个类而不是抽象类。
谢谢
【问题讨论】:
一个问题可能是您的 RestResponsePage 类是一个抽象类型。如果您查看***.com/a/44895867/3362244,您会注意到HelperPage 类不是一般类型的。相反,它是一个通用类型抽象类的具体实现,Jackson 可以对其进行反序列化。这是它起作用的关键原因之一。 @jtcotton63 没有骰子,我得到了同样的错误。 你试过从类声明中删除implements Serializable
吗?
@Venky 对我来说这行不通。
【参考方案1】:
我找到了一种更优雅的方法来解决这个问题。其实是jackson序列化的问题。
首先定义一个jackson的模块:
public class PageJacksonModule extends Module
@Override
public String getModuleName()
return "PageJacksonModule";
@Override
public Version version()
return new Version(0,1,0, "", null,null);
@Override
public void setupModule(SetupContext context)
context.setMixInAnnotations(Page.class, PageMixIn.class);
@JsonDeserialize(as = SimplePageImpl.class)
private interface PageMixIn
static class SimplePageImpl<T> implements Page<T>
private final Page<T> delegate;
public SimplePageImpl(
@JsonProperty("content") List<T> content,
@JsonProperty("page")int number,
@JsonProperty("size") int size,
@JsonProperty("totalElements") long totalElements)
delegate = new PageImpl<>(content, PageRequest.of(number, size), totalElements);
@JsonProperty
@Override
public int getTotalPages()
return delegate.getTotalPages();
@JsonProperty
@Override
public long getTotalElements()
return delegate.getTotalElements();
@JsonProperty("page")
@Override
public int getNumber()
return delegate.getNumber();
@JsonProperty
@Override
public int getSize()
return delegate.getSize();
@JsonProperty
@Override
public int getNumberOfElements()
return delegate.getNumberOfElements();
@JsonProperty
@Override
public List<T> getContent()
return delegate.getContent();
@JsonProperty
@Override
public boolean hasContent()
return delegate.hasContent();
@JsonIgnore
@Override
public Sort getSort()
return delegate.getSort();
@JsonProperty
@Override
public boolean isFirst()
return delegate.isFirst();
@JsonProperty
@Override
public boolean isLast()
return delegate.isLast();
@JsonIgnore
@Override
public boolean hasNext()
return delegate.hasNext();
@JsonIgnore
@Override
public boolean hasPrevious()
return delegate.hasPrevious();
@JsonIgnore
@Override
public Pageable nextPageable()
return delegate.nextPageable();
@JsonIgnore
@Override
public Pageable previousPageable()
return delegate.previousPageable();
@JsonIgnore
@Override
public <U> Page<U> map(Function<? super T, ? extends U> converter)
return delegate.map(converter);
@JsonIgnore
@Override
public Iterator<T> iterator()
return delegate.iterator();
然后注入它:
@Configuration
public class JacksonConfig
@Bean
public Module pageJacksonModule()
return new PageJacksonModule();
最后,你可以使用Page
对象了。
当然是测试:
@RunWith(SpringJUnit4ClassRunner.class)
@JsonTest
public class PageImplTest
@Autowired
ObjectMapper mapper;
@Test
public void page() throws IOException
String inputPageStr = "\"content\":[\"name\":\"n1\",\"gender\":\"boy\",\"age\":23,\"name\":\"n2\",\"gender\":\"girl\",\"age\":20],\"pageable\":\"INSTANCE\",\"totalPages\":1,\"last\":true,\"totalElements\":2,\"sort\":\"sorted\":false,\"unsorted\":true,\"empty\":true,\"first\":true,\"numberOfElements\":2,\"size\":2,\"number\":0,\"empty\":false";
Page<PageItem> pageItems = mapper
.readValue(inputPageStr, new TypeReference<Page<PageItem>>() );
Assert.assertNotNull(pageItems);
static class PageItem
private String name;
private String gender;
private int age;
public PageItem()
public PageItem(String name, String gender, int age)
this.name = name;
this.gender = gender;
this.age = age;
public String getName()
return name;
public void setName(String name)
this.name = name;
public String getGender()
return gender;
public void setGender(String gender)
this.gender = gender;
public int getAge()
return age;
public void setAge(int age)
this.age = age;
@Configuration
static class TestConfig
@Bean
public Module pageJacksonModule()
return new PageJacksonModule();
【讨论】:
【参考方案2】:package com.td.support;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.JsonNode;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import java.util.ArrayList;
import java.util.List;
public class RestResponsePage<T> extends PageImpl<T>
@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
public RestResponsePage(@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 RestResponsePage(List<T> content, Pageable pageable, long total)
super(content, pageable, total);
public RestResponsePage(List<T> content)
super(content);
public RestResponsePage()
super(new ArrayList<>());
spring boot 2.0 上面可能没问题..
【讨论】:
对于任何需要更多上下文的人,构造函数需要具有所有这些参数,即使它们没有被使用。从 spring boot 1.5.x 升级到 2.x 时我遇到了这个问题 我的测试类面临完全相同的问题,我的 spring-boot 版本是以上是关于使用 Pageable 字段测试端点时出现 JsonMappingException的主要内容,如果未能解决你的问题,请参考以下文章
使用 @SpringBootTest 测试时出现 HttpMessageNotWritableException
创建对 URL 端点的订阅时出现“pubsub 错误 INVALID_ARGUMENT”
Google PubSub:使用 AppEngine 推送端点订阅时出现 SSL 错误
将 DAX 与 DynamoDB 一起使用时出现无法配置集群端点错误