spring-projects / spring-graphql

Spring Integration for GraphQL
https://spring.io/projects/spring-graphql
Apache License 2.0
1.53k stars 306 forks source link

JsonKeysetCursorStrategy throws DecodingException when keys map contains value of type java.lang.Long #1047

Closed mspiess closed 3 months ago

mspiess commented 3 months ago

Minimum example:

var strategy = new JsonKeysetCursorStrategy();
var cursor = strategy.toCursor(Map.of("id", 1L));
strategy.fromCursor(cursor); // throws

Stacktrace:

org.springframework.core.codec.DecodingException: JSON decoding error: Could not resolve type id 'java.lang.Long' as a subtype of `java.lang.Object`: Configured `PolymorphicTypeValidator` (of type `com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator`) denied resolution

    at org.springframework.http.codec.json.AbstractJackson2Decoder.processException(AbstractJackson2Decoder.java:275)
    at org.springframework.http.codec.json.AbstractJackson2Decoder.decode(AbstractJackson2Decoder.java:211)
    at org.springframework.graphql.data.query.JsonKeysetCursorStrategy.fromCursor(JsonKeysetCursorStrategy.java:131)
    at com.example.demo.DecoderTest.strategy(DecoderTest.java:40)
    at java.base/java.lang.reflect.Method.invoke(Method.java:580)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
Caused by: com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Could not resolve type id 'java.lang.Long' as a subtype of `java.lang.Object`: Configured `PolymorphicTypeValidator` (of type `com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator`) denied resolution
 at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1, column: 64] (through reference chain: java.util.LinkedHashMap["id"])
    at com.fasterxml.jackson.databind.exc.InvalidTypeIdException.from(InvalidTypeIdException.java:43)
    at com.fasterxml.jackson.databind.DeserializationContext.invalidTypeIdException(DeserializationContext.java:2040)
    at com.fasterxml.jackson.databind.DatabindContext._throwSubtypeClassNotAllowed(DatabindContext.java:301)
    at com.fasterxml.jackson.databind.DatabindContext.resolveAndValidateSubType(DatabindContext.java:258)
    at com.fasterxml.jackson.databind.jsontype.impl.ClassNameIdResolver._typeFromId(ClassNameIdResolver.java:75)
    at com.fasterxml.jackson.databind.jsontype.impl.ClassNameIdResolver.typeFromId(ClassNameIdResolver.java:69)
    at com.fasterxml.jackson.databind.jsontype.impl.TypeDeserializerBase._findDeserializer(TypeDeserializerBase.java:159)
    at com.fasterxml.jackson.databind.jsontype.impl.AsArrayTypeDeserializer._deserialize(AsArrayTypeDeserializer.java:97)
    at com.fasterxml.jackson.databind.jsontype.impl.AsArrayTypeDeserializer.deserializeTypedFromAny(AsArrayTypeDeserializer.java:71)
    at com.fasterxml.jackson.databind.deser.std.UntypedObjectDeserializerNR.deserializeWithType(UntypedObjectDeserializerNR.java:112)
    at com.fasterxml.jackson.databind.deser.std.MapDeserializer._readAndBindStringKeyMap(MapDeserializer.java:625)
    at com.fasterxml.jackson.databind.deser.std.MapDeserializer.deserialize(MapDeserializer.java:449)
    at com.fasterxml.jackson.databind.deser.std.MapDeserializer.deserialize(MapDeserializer.java:32)
    at com.fasterxml.jackson.databind.deser.std.StdDelegatingDeserializer.deserialize(StdDelegatingDeserializer.java:170)
    at com.fasterxml.jackson.databind.jsontype.impl.AsArrayTypeDeserializer._deserialize(AsArrayTypeDeserializer.java:120)
    at com.fasterxml.jackson.databind.jsontype.impl.AsArrayTypeDeserializer.deserializeTypedFromObject(AsArrayTypeDeserializer.java:61)
    at com.fasterxml.jackson.databind.deser.std.MapDeserializer.deserializeWithType(MapDeserializer.java:492)
    at com.fasterxml.jackson.databind.deser.impl.TypeWrappedDeserializer.deserialize(TypeWrappedDeserializer.java:74)
    at com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.readRootValue(DefaultDeserializationContext.java:342)
    at com.fasterxml.jackson.databind.ObjectReader._bindAndClose(ObjectReader.java:2125)
    at com.fasterxml.jackson.databind.ObjectReader.readValue(ObjectReader.java:1501)
    at org.springframework.http.codec.json.AbstractJackson2Decoder.decode(AbstractJackson2Decoder.java:206)
    ... 5 more

Tested on versions 1.3.1 & 1.3.2.

mspiess commented 3 months ago

Nevermind, this is by design: https://docs.spring.io/spring-graphql/reference/data.html#data.pagination.scroll.keyset The complete EncodingCursorStrategy needs has to be provided in order to change this:

    @Bean
    fun cursorStrategy(): EncodingCursorStrategy<ScrollPosition> {
        val validator: PolymorphicTypeValidator = BasicPolymorphicTypeValidator.builder()
            .allowIfBaseType(Map::class.java)
            .allowIfSubType(java.lang.Long::class.java)
            .build()

        val mapper = ObjectMapper()
        mapper.activateDefaultTyping(validator, ObjectMapper.DefaultTyping.NON_FINAL)

        val configurer: CodecConfigurer = ServerCodecConfigurer.create()
        configurer.defaultCodecs().jackson2JsonDecoder(Jackson2JsonDecoder(mapper));
        configurer.defaultCodecs().jackson2JsonEncoder(Jackson2JsonEncoder(mapper));

        val jsonKeysetCursorStrategy = JsonKeysetCursorStrategy(configurer)
        return CursorStrategy.withEncoder(ScrollPositionCursorStrategy(jsonKeysetCursorStrategy), CursorEncoder.base64())
    }