RedisTemplate hashvalue serializer to use for nested object with multiple types

13,385

The Jackson2JsonRedisSerializer does not include mapping information into the actual hash structure. The resulting Redis HASH results in something like:

127.0.0.1:6379> hgetall job:1
1) "id"
2) "\"1\""
3) "createTime"
4) "1455778716799"
5) "submitterName"
6) "\"Jon Snow\""
7) "jobDef"
8) "{\"def\":\"nightwatch\"}"

The ObjectMapper produces a LinkedHashMap for the JobDefinition entry which fails to deserialize as the type is unknown.

Using the GenericJackson2JsonRedisSerializer includes type information so the resulting Redis HASH looks like this:

127.0.0.1:6379> hgetall job:1
1) "id"
2) "\"1\""
...
7) "jobDef"
8) "{\"@class\":\"java.util.LinkedHashMap\",\"def\":\"nightwatch\"}"

This allows to deserialize values correctly.

Another approach would be to NOT use a specific HashValueSerializer but instead use a DecoratingStringHashMapper along with the StringRedisTemplate.

DecoratingStringHashMapper mapper = new DecoratingStringHashMapper<Job>(
  new JacksonHashMapper<Job>(Job.class));

template.opsForHash().putAll("job:" + job.id, mapper.toHash(job));
Map jobMap = template.opsForHash().entries("job:" + job.id); 

The DecoratingStringHashMapper will produce a Redis Hash as follows:

127.0.0.1:6379> hgetall job:1
1) "id"
2) "1"
3) "createTime"
4) "1455780810643"
5) "submitterName"
6) "Jon Snow"
7) "jobDef"
8) "{def=nightwatch}"

Unfortunately there is no Jackson2HashMapper. Please vote for DATAREDIS-423 and help us prioritize.

Share:
13,385
Derek
Author by

Derek

Updated on June 18, 2022

Comments

  • Derek
    Derek almost 2 years

    I'm trying to use Redis to store some cache data for my entity, which has different types of fields inside, for example,

    public class Job {
        private String id;
        private Date createTime; //Long
        private String submitterName;
        private JobDefinition jobDef;  //Another class
    }
    

    There are more fields and due to the fact that several fields are updated more frequently than others, I decided to save this job as a Hashmap in Redis with each field as a key. Here the nested object like jobDef is not important so I used Jackson2JsonRedisSerializer as hashValueSerializer for RedisTemplate and the jobDef obj will just be serialized as a long JSON string, which is totally fine in my case.

    But I don't know how can I effectively deserialize the whole job object back from Redis. The type I set to deserializer is like Jackson2JsonRedisSerializer(Map.class) but it complains when deserializing String keys and values.

    So is this an invalid usage with RedisTemplate or how should I configure my serializer for it?

    EDIT: Adding more code details,

        @Autowired
        private StringRedisTemplate redisTemplate; //Here I'm using a String template as I need to use the same redisTemplate for some key-value/list operations too
    
        Map jobHash= new ObjectMapper().convertValue(job, Map.class);
    
        redisTemplate.setHashValueSerializer(new Jackson2JsonRedisSerializer(Map.class));
    
        redisTemplate.opsForHash().putAll("job:"+job.getId(), jobHash); //After this the job hash shows up in Redis as I expected, while the jobDef member is serialized and saved as a JSON string
    
        Map jobMap = redisTemplate.opsForHash().entries("job:" + job.getId()); //But this won't work as it'll throw exception complaining cannot deserialize a String value to Map. But when I set Jackson2JsonRedisSerializer(String.class) it throws exception that cannot resolve the byte code
    

    2nd EDIT:

    If using JdkSerializationRedisSerializer as HashValueSerializer in RedisTemplate then the deserialization works fine, however the downside for using this one is the value stored in Redis is not the same human readable string value as when using Jackson2JsonRedisSerializer.

  • Derek
    Derek about 8 years
    Thanks a lot for the help! Since I'm running on Spring-data-redis 1.4.2 seems to me GenericJackson2JsonRedisSerializer is not available yet. The second approach almost works like magic except the hash converted has createTime missing, is there any problem converting non-string value? I noticed my original line of Map jobHash= new ObjectMapper().convertValue(job, Map.class); uses a different ObjectMapper (com.fasterxml.jackson.databind.ObjectMapper#ObjectMapper()) and it can generate date value in hash.
  • Christoph Strobl
    Christoph Strobl about 8 years
    The JacksonHashMapper still uses the org.codehaus.jackson.map.ObjectMapper. You can try passing in your own configuration of that one - I did not have trouble serializing java.util.Date values using it with the defaults. The code for GenericJackson2JsonRedisSerializer is on github, so you might just want to copy it into your project in case you cannot upgrade to a more recent version.