Reputation: 1017
The problem: A feign client, making an API call to a Spring boot Rest API that returns a Page<T>
can't deserialize the sort
property of that page.
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of
org.springframework.data.domain.Sort
(no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator) at [Source: (BufferedReader); line: 1, column: 238] (through reference chain: org.springframework.cloud.openfeign.support.PageJacksonModule$SimplePageImpl["sort"])
Not sure why the registered PageJacksonModule
doesn't seem to support that.
Given a manually configured Feign client:
public class TelematicsConfig {
private String host;
ObjectMapper provideObjectMapper() {
return new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.setPropertyNamingStrategy(SnakeCaseStrategy.SNAKE_CASE)
.registerModule(new PageJacksonModule());
}
@Bean
TelematicsClient provideTelematicsClient() {
return Feign.builder()
.client(new OkHttpClient())
.encoder(new JacksonEncoder(provideObjectMapper()))
.decoder(new JacksonDecoder(provideObjectMapper()))
.logger(new Slf4jLogger(TelematicsClient.class))
.logLevel(Logger.Level.FULL)
.target(TelematicsClient.class, host);
}
}
The client itself:
public interface TelematicsClient {
@RequestLine("GET /api/v1/telematics/devices")
Page<TelematicsDevice> getDevices();
}
When invoking this I get:
2020-09-16 12:38:49.707 ERROR 96244 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is feign.FeignException: Cannot construct instance of `org.springframework.data.domain.Sort` (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
at [Source: (BufferedReader); line: 1, column: 238] (through reference chain: org.springframework.cloud.openfeign.support.PageJacksonModule$SimplePageImpl["sort"]) reading GET http://localhost:8081/api/v1/telematics/devices] with root cause
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `org.springframework.data.domain.Sort` (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
at [Source: (BufferedReader); line: 1, column: 238] (through reference chain: org.springframework.cloud.openfeign.support.PageJacksonModule$SimplePageImpl["sort"])
at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67)
at com.fasterxml.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1611)
at com.fasterxml.jackson.databind.DatabindContext.reportBadDefinition(DatabindContext.java:400)
at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1077)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1320)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:331)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:164)
at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:542)
at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeWithErrorWrapping(BeanDeserializer.java:535)
at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeUsingPropertyBased(BeanDeserializer.java:419)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1310)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:331)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:164)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4482)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3463)
at feign.jackson.JacksonDecoder.decode(JacksonDecoder.java:61)
Any insights into why this isn't working would be greatly appreciated.
Edit: The following class seems to hint at support for sorting, no?
Upvotes: 5
Views: 17091
Reputation: 488
My solution to solve Sort
and Page
issues with feign client is like the bellow steps:
Create class of type CustomPageDeserializer<T>
:
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.ObjectMapper;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import java.io.IOException;
import java.util.List;
public class CustomPageDeserializer<T> extends JsonDeserializer<PageImpl<T>> {
private final Class<T> contentClass;
public CustomPageDeserializer(Class<T> contentClass) {
this.contentClass = contentClass;
}
@Override
public PageImpl<T> deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
ObjectMapper mapper = (ObjectMapper) jsonParser.getCodec();
JsonNode root = mapper.readTree(jsonParser);
JsonNode content = root.get("content");
List<T> contentList = mapper.readValue(content.traverse(), mapper.getTypeFactory().constructCollectionType(List.class, contentClass));
int totalElements = root.get("totalElements").asInt();
int totalPages = root.get("totalPages").asInt();
int number = root.get("number").asInt();
int pageSize = contentList.isEmpty() ? 1 : contentList.size();
return new PageImpl<>(contentList, PageRequest.of(number, pageSize), totalElements);
} }
Create class CustomPageModule
:
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.springframework.data.domain.PageImpl;
public class CustomPageModule extends SimpleModule {
public CustomPageModule(Class<?> contentClass) {
addDeserializer(PageImpl.class, new CustomPageDeserializer<>(contentClass));
} }
Create class MyFeignClientConfiguration
:
import com.fasterxml.jackson.databind.ObjectMapper;
import feign.codec.Decoder;
import feign.optionals.OptionalDecoder;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.cloud.openfeign.support.ResponseEntityDecoder;
import org.springframework.cloud.openfeign.support.SpringDecoder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
@Configuration
public class MyFeignClientConfiguration {
@Bean
public Decoder feignDecoder() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new CustomPageModule(MyEntity.class));
return new OptionalDecoder(new ResponseEntityDecoder(new SpringDecoder(() -> new HttpMessageConverters(new
MappingJackson2HttpMessageConverter(objectMapper)))));
} }
Create class MyFeignClient
:
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import java.util.List;
@FeignClient(name = "myFeignClient", url =
"${my.service.feign.url}", configuration =
MyFeignClientConfiguration.class)
public interface MyFeignClient {
@GetMapping("/api/action/products")
ResponseEntity<List<String>> getAvailableProducts(); }
Upvotes: 0
Reputation: 4121
You can get these modules by adding the openfeign-core dependency:
implementation "org.springframework.cloud:spring-cloud-openfeign-core"
But this doesn't work in Spring Boot 3 for the SortJacksonModule
anymore.
private val mapper = jacksonObjectMapper().registerModules(
PageJacksonModule(),
SortJacksonModule(),
JavaTimeModule(),
)
java.lang.NoClassDefFoundError: feign/codec/EncodeException at org.springframework.cloud.openfeign.support.SortJacksonModule.setupModule(SortJacksonModule.java:47)
The OpenFeign spring team are waiting for votes in order to spend the time to fix this issue. Make sure to vote on the issue here:
https://github.com/spring-cloud/spring-cloud-openfeign/issues/675
Upvotes: 1
Reputation: 2200
try to add :
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-openfeign-core</artifactId>
<version>3.1.3</version>
<scope>compile</scope>
</dependency>
Upvotes: 0
Reputation: 22333
If you use autoconfigured Feign client you can follow Spring Cloud OpenFeign docs by turning on corresponding configuration property:
You may consider enabling Jackson Modules for the support
org.springframework.data.domain.Page
andorg.springframework.data.domain.Sort
decoding.feign.autoconfiguration.jackson.enabled=true
Upvotes: 18
Reputation: 1017
Found the answer.
return new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.setPropertyNamingStrategy(SnakeCaseStrategy.SNAKE_CASE)
.registerModule(new PageJacksonModule())
.registerModule(new SortJacksonModule()); // <-- This. duh.
Upvotes: 11