Search code examples
javajcacheredissonjsr107

Redisson (through JCache API) deserialising only to String


I have a Redisson client to store a pair String, LocalDateTime. It's configured to be used through the JCache API (JSR-107).

The storage is done OK, converting with Jackson to values like 2018-01-23T11:59:34.997834 but for retrieval is not using any converter and returning a String, giving a ClassCastException in the cache#get invocation.

What am I missing here?

@Test
public void getCacheInline() {
    Config redissonCfg = new Config();
    redissonCfg
        .setCodec(new JsonJacksonCodec(buildObjectMapper()))
        .useSingleServer()
        .setAddress("redis://redis:6379");

    MutableConfiguration<String, LocalDateTime> jcacheConfig = new MutableConfiguration<String, LocalDateTime>()
        .setTypes(String.class, LocalDateTime.class)
        .setExpiryPolicyFactory((Factory<ExpiryPolicy>) () -> new CreatedExpiryPolicy(new Duration(SECONDS, 100)));

    Configuration<String, LocalDateTime> configuration = RedissonConfiguration.fromConfig(redissonCfg, jcacheConfig);


    Cache<String, LocalDateTime> cache = cacheManager.createCache(CACHE_NAME, configuration);
    LocalDateTime expectedDateTime = LocalDateTime.now();
    cache.put("testKey", expectedDateTime);

    // In this line: java.lang.ClassCastException: java.base/java.lang.String cannot be cast to java.base/java.time.LocalDateTime
    LocalDateTime actualDateTime = cache.get("testKey");
    assertThat(actualDateTime, is(equalTo(expectedDateTime)));
}

private ObjectMapper buildObjectMapper() {
    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.registerModule(new JavaTimeModule());
    objectMapper.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);
    objectMapper.configure(WRITE_DATES_AS_TIMESTAMPS, false);
    objectMapper.configure(READ_DATE_TIMESTAMPS_AS_NANOSECONDS, false);
    objectMapper.setSerializationInclusion(NON_NULL);
    return objectMapper;
}

Solution

  • Initial workaround was instead of storing a pair String, LocalDateTime, wrapping the LocalDateTime in a wrapper class:

    public class LocalDateTimeWrapper {
        private LocalDateTime value;
        ...
    }
    

    This would make Jackson serialize a json string with a @class property signalling the LocalDateTimeWrapper class, and from there LocalDateTime can be retrieved as the type to deserialize the string like 2018-01-23T11:59:34.997834 to.

    Better solution that I've tried and worked for me is this one suggested in GitHub issue https://github.com/redisson/redisson/issues/1260#issuecomment-367272400 extending JsonJacksonMapCodec like this:

    public static class ExtendedJsonJacksonMapCodec extends JsonJacksonMapCodec {
    
        public ExtendedJsonJacksonMapCodec() {
            super(String.class, LocalDateTime.class);
        }
    
        @Override
        protected void init(ObjectMapper objectMapper) {
            objectMapper.registerModule(new JavaTimeModule());
            super.init(objectMapper);
        }
    
    }
    

    and then linking it from config like this (YAML format):

    singleServerConfig:
        address: "redis://localhost:6379"
    codec: !<com.example.ExtendedJsonJacksonMapCodec> {}