FasterXML / jackson-databind

General data-binding package for Jackson (2.x): works on streaming API (core) implementation(s)
Apache License 2.0
3.52k stars 1.38k forks source link

Unable to register JDK8 module with GenericJackson2JsonRedisSerializer API, but it is required for Optional support. #4600

Open mkowaliszyn-coursera opened 3 months ago

mkowaliszyn-coursera commented 3 months ago

Search before asking

Describe the bug

The GenericJackson2JsonRedisSerializer implementation does not permit the registration of modules with the existing implementation. Since this PR #4082 now blows up due to JDK8 Module being not registered between versions 2.15.2 and 2.16.0 where before serializing Optional worked without the module (or the module was auto registered, not clear).

The error thrown is:

org.springframework.data.redis.serializer.SerializationException: Could not write JSON: Java 8 optional type `java.util.Optional<java.lang.String>` not supported by default: add Module "com.fasterxml.jackson.datatype:jackson-datatype-jdk8" to enable handling

The implementation should permit customization of the underlying ObjectMapper, but it only permits complete override or nothing. The package private implementations of the serializer make it impossible to extend or reconfigure.

The workaround is to use reflection to register the module:


  @NotNull
  public RedisCacheConfiguration getRedisCacheConfiguration(
      final String prefixName, final int minutes) {
    return RedisCacheConfiguration.defaultCacheConfig()
        .entryTtl(Duration.ofMinutes(minutes))
        .prefixCacheNameWith(prefixName)
        .disableCachingNullValues()
        .serializeValuesWith(
            RedisSerializationContext.SerializationPair.fromSerializer(configureSerializer()));
  }

 private GenericJackson2JsonRedisSerializer configureSerializer() {
    final var serializer = new GenericJackson2JsonRedisSerializer();

    try {
      final Field field = serializer.getClass().getDeclaredField("mapper");
      field.setAccessible(true);
      final ObjectMapper mapper = (ObjectMapper) field.get(serializer);
      mapper.registerModule(new Jdk8Module());
    } catch (NoSuchFieldException | IllegalAccessException e) {
      throw new RuntimeException(
          "Failed to hack the GenericJackson2JsonRedisSerializer to register the databind JDK8 module",
          e);
    }

    return serializer;
  }

Version Information

2.16.0+

Reproduction

This code uses private models, but conveys the gist of the problem: the model takes an Optional field. This worked prior to 2.16.0.

@Test
  public void testSerializeOptional() {
    OpenCourseCmlAsset asset =
        new OpenCourseCmlAsset(new CmlContent("dtdId", "value"), Optional.of("testVal"));
    var serialized = redisConfig.getValueSerializationPair().write(asset);

    Assertions.assertNotNull(serialized);
  }

Expected behavior

I can register the JDK8 module and other modules with GenericJackson2JsonRedisSerializer and other implementations provided.

Additional context

No response

cowtowncoder commented 3 months ago

Wrong repo: GenericJackson2JsonRedisSerializer is not part of Jackson so this needs to be filed somewhere else.

pjfanning commented 3 months ago

Looks like a Spring class.

https://docs.spring.io/spring-data/redis/docs/current/api/org/springframework/data/redis/serializer/GenericJackson2JsonRedisSerializer.html

To reiterate, this needs to be reported to Spring and not here.