spring-cloud-openfeign
spring-cloud-openfeign copied to clipboard
Inconsistent Pageable Sort serialization between openfeign and spring mvc
- spring boot version: 2.6.3
- spring cloud version: 2021.0.0 (spring cloud openfeign 3.1.0)
spring serializes Sort
as an object
"sort": { "empty": true, "sorted": false, "unsorted": true }
when SortJacksonModule
is enabled (feign.autoconfiguration.jackson.enabled: true
) Sort
is serialize as an array by SortJsonComponent.SortSerializer
.
"sort": []
since SortJacksonModule
is in the spring context the controller response includes the Sort array creating inconsistency between the controller response with and without SortJacksonModule
Hello @philippeu, thanks for reporting this. I was able to reproduce the issue. Will work on it.
@philippeu Have looked into this further. Actually, the data that comes from the sort json of this kind: { "empty": true, "sorted": false, "unsorted": true }
is not enough to construct a viable Sort
object. If you do not set feign.autoconfiguration.jackson.enabled: true
, you will simply get an exception, cause it cannot be deserialised. That's probably why @cbezmen has opted for returning null
in such a scenario. Could you provide some more details regarding your usecase?
Hi Olga,
I have a service returning Pageable.
by default MappingJackson2HttpMessageConverter
, serialize Sort
object to (using PageImpl
)
"sort": { "empty": true, "sorted": false, "unsorted": true }
This service is calling another one via a feign client which is also returning a Pageable
feign.autoconfiguration.jackson.enabled
must be set to true
in order for the feign client to deserialize the pagination.
once feign jackson is enabled, it registered the SortJacksonModule
deserializer and it can read the paginated response.
this is good
the SortJacksonModule
serializer is also registered in ObjectMapper
of MappingJackson2HttpMessageConverter
and then used to serialize the Sort
object to
"sort": []
so services having feign jackson enabled do not provide the same response format (sort is serialized as an array) than the ones having feign jackson disabled (sort is serialized as an object).
I work around that by providing a CustomSortJacksonModule
serializer and CustomSortJsonComponent
.
@philippeu Could you please provide the code of CustomSortJacksonModule
serializer and CustomSortJsonComponent
? It will be easier for us to understand your use-case then.
Here they are:
public class CustomSortJacksonModule extends SortJacksonModule {
@Override
public String getModuleName() {
return "SortModule";
}
@Override
public Version version() {
return new Version(0, 1, 0, "", null, null);
}
@Override
public void setupModule(SetupContext context) {
var serializers = new SimpleSerializers();
serializers.addSerializer(Sort.class, new CustomSortJsonComponent.SortSerializer());
context.addSerializers(serializers);
var deserializers = new SimpleDeserializers();
deserializers.addDeserializer(Sort.class, new CustomSortJsonComponent.SortDeserializer());
context.addDeserializers(deserializers);
}
}
public class CustomSortJsonComponent {
public static class SortSerializer extends JsonSerializer<Sort> {
private record CustomSort(boolean empty, boolean sorted, boolean unsorted) {};
@Override
public void serialize(Sort value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeObject(new CustomSort(value.isEmpty(), value.isSorted(), value.isUnsorted()));
}
@Override
public Class<Sort> handledType() {
return Sort.class;
}
}
public static class SortDeserializer extends JsonDeserializer<Sort> {
@Override
public Sort deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
var treeNode = jsonParser.getCodec().readTree(jsonParser);
if (treeNode.isArray()) {
var arrayNode = (ArrayNode) treeNode;
var orders = new ArrayList<Sort.Order>();
arrayNode.forEach(jsonNode -> orders.add(new Sort.Order(Sort.Direction.valueOf(jsonNode.get("direction").textValue()), jsonNode.get("property").textValue())));
return Sort.by(orders);
}
return null;
}
@Override
public Class<Sort> handledType() {
return Sort.class;
}
}
}
a better solution would be to have an specific object mapper for feign not interfering with spring one maybe.
@philippeu - thanks for providing the samples. I thought you meant deserialising, cause when a Sort
body is passed on, unless you provide an Encoder
bean different from PageableSpringEncoder
, it will not be serialised, but passed as request params instead. The current serialisation provides the bits necessary to do the sorting. Since you have a viable workaround, we're going to wait with this to see if there's any more interest in it within the community.
ok, thanks Olga!