spring-data-redis icon indicating copy to clipboard operation
spring-data-redis copied to clipboard

Add support for deserializing objects with generics

Open JosRoseboom opened this issue 3 years ago • 2 comments

Currently there is no RedisSerializer that can deserialize an object with generics like Set<Person>. This is for example a problem when using redis as cache in spring boot:

 @Cacheable("personCache)
 public Set<Person> getPersons()  {
   // expensive code
 }

Using for example Jackson2JsonRedisSerializer, we could specify Set as type, but it won't handle the Person type. It deserializes to Set<LinkedHashMap> with the LinkedHashMap having the properties of the person. A cache hit results in a ClassCastException because a LinkedHashMap cannot be cast to a Person (similarly, a Set of enums is deserialized as a Set of Strings).

Classes using a generic (Person in this case) should not be forced to implement Serializable, so Jackson based serialization in itself is nice.

Note: Created this issue to refer to in pull request: pull request on the way

JosRoseboom avatar Jul 28 '22 10:07 JosRoseboom

Generics are not retained for values, therefore the cache implementation cannot know anything about the type definition. Spring's Cache API does not support generic type hints either, see https://github.com/spring-projects/spring-framework/blob/main/spring-context/src/main/java/org/springframework/cache/Cache.java#L82.

I think using Jackson should work when using type hints, other than that, there's not much we can do here. I'll leave this one open once your PR arrives.

mp911de avatar Jul 28 '22 12:07 mp911de

Hi Mark,

thanks for your fast response. I created a pull request: #2375

One of the tests I added shows a serializer that could make the Set<Person> case in my original post work:

@Test // GH-2374
void testSetOfPersons() {
	PersonObjectFactory personFactory = new PersonObjectFactory();
	Set<Person> personSet = Set.of(personFactory.instance(), personFactory.instance());
	JacksonTypeReference2JsonRedisSerializer<Set<Person>> personSetSerializer = 
                  new JacksonTypeReference2JsonRedisSerializer<>(new TypeReference<>() {});

	assertThat(personSetSerializer.deserialize(personSetSerializer.serialize(personSet))).isEqualTo(personSet);
}

In a Spring project that uses Redis as cache for @Cacheable, in a configuration Bean for redis cache, I could do something like:

@Configuration
public class RedisCacheConfig {

    @Bean
    public RedisCacheManagerBuilderCustomizer redisCacheManagerBuilderCustomizer() {
        JacksonTypeReference2JsonRedisSerializer<Set<Person>> serializer = new JacksonTypeReference2JsonRedisSerializer<>(new TypeReference<>() {
        });
        RedisCacheConfiguration conf = RedisCacheConfiguration.defaultCacheConfig()
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(serializer));
        return builder -> builder.withCacheConfiguration("personCache", conf);
    }

}


Now JacksonTypeReference2JsonRedisSerializer<Set<Person>> deserializes the bytes of the redis DB to a Set<Person> instead of a Set<LinkedHashMap> and the ClassCastException is gone.

Currently solved it in a project this way and it would be nice to make it a bit easier for others to handle this case, hence this PR.

JosRoseboom avatar Jul 28 '22 13:07 JosRoseboom

Closing as per https://github.com/spring-projects/spring-data-redis/pull/2375#issuecomment-1541619462

mp911de avatar May 10 '23 12:05 mp911de