Files
spring-boot-demo/redis/docs/04-json-serializers.md
T

4.4 KiB

Home | Prev: String template and two keyspaces | Next: @RedisHash

4. JSON serializers: readable values, Jackson 3

The usual fix for unreadable values is JSON. Spring Data Redis 4.1 has two families of JSON serializer, and the names are easy to mix up (16-serializers-javap.txt, read from the jar):

Class Jackson Status
GenericJackson2JsonRedisSerializer 2 @Deprecated(since = "4.0")
Jackson2JsonRedisSerializer<T> 2 @Deprecated(since = "4.0")
GenericJacksonJsonRedisSerializer 3 (tools.jackson) not deprecated
JacksonJsonRedisSerializer<T> 3 (tools.jackson) not deprecated

Jackson in the name means Jackson 3. Jackson2 means Jackson 2, and it is what most examples on the web still show. The Redis starter includes neither (chapter 1); add spring-boot-starter-jackson for Jackson 3.

Typed: JacksonJsonRedisSerializer<T>

One value type per template, plain JSON, nothing extra (06-jackson-serializers.txt):

$ redis-cli -p 6390 GET user:1
{"id":1,"name":"Ankur"}

The constructors are (Class), (JavaType), (ObjectMapper, Class) and so on. The cost is one template bean per value type. userRedisTemplate is the example.

Generic: GenericJacksonJsonRedisSerializer

One template for any value type. To read a value back as the right class it has to know the class, so it needs default typing, which writes a type property into every value:

$ redis-cli -p 6390 GET user:2
{"@class":"com.ankurm.redis.model.User","id":2,"name":"Ankur"}

Without typing configured (GenericJacksonJsonRedisSerializer.builder().build()), the JSON has no @class, and reading it back returns a LinkedHashMap, not a User:

read back: {id=3, name=Ankur} (LinkedHashMap)

The builder offers enableDefaultTyping(PolymorphicTypeValidator), enableUnsafeDefaultTyping(), typeValidator(...), typePropertyName(...), customize(Consumer) and more.

The @class property is input

Whatever is in Redis chooses the class the serializer instantiates. Anyone who can write to that Redis (another service, an operator, an attacker with network access to an unauthenticated instance) can write {"@class": ...} for any class on your classpath. That is why the builder has a method called enableUnsafeDefaultTyping and why enableDefaultTyping takes a validator. JsonRedisConfig allows only subtypes under com.ankurm.redis., and 07-jackson-untrusted-class.txt shows what happens to a class outside it:

$ redis-cli -p 6390 SET user:evil '{"@class":"com.ankurm.other.Outsider","name":"x"}'
OK
...
message: Could not read JSON: Could not resolve type id 'com.ankurm.other.Outsider' as a subtype of `com.ankurm.other.Outsider`: Configured `PolymorphicTypeValidator` (of type `tools.jackson.databind.jsontype.BasicPolymorphicTypeValidator`) denied resolution

The same transcript shows the other failure you will meet: a class inside the allow-list that no longer exists (renamed, moved) fails with Cannot locate class. Type names in Redis are a compatibility surface, exactly as the class name inside a JDK-serialised value was.

Which one?

Typed Generic
redis-cli output plain JSON JSON with @class
Other languages can read it yes yes, if they ignore @class
Rename or move the class safe breaks reads
Needs an allow-list no yes
Templates needed one per type one

If a Redis is shared between services, prefer typed, or configure typePropertyName and an allow-list deliberately. If it is private to one application, generic with a validator is fine.

Going deeper

Home | Prev: String template and two keyspaces | Next: @RedisHash