Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01B38FGKKam5SCGgwgduVAh3
94 lines
4.4 KiB
Markdown
94 lines
4.4 KiB
Markdown
[Home](../README.md) | Prev: [String template and two keyspaces](03-string-template-and-two-keyspaces.md) | Next: [@RedisHash](05-redis-hash.md)
|
|
|
|
# 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](output/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](01-what-boot-gives-you.md)); add
|
|
`spring-boot-starter-jackson` for Jackson 3.
|
|
|
|
## Typed: JacksonJsonRedisSerializer<T>
|
|
|
|
One value type per template, plain JSON, nothing extra
|
|
([06-jackson-serializers.txt](output/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](../src/main/java/com/ankurm/redis/config/JsonRedisConfig.java) 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](../src/main/java/com/ankurm/redis/config/JsonRedisConfig.java) allows only subtypes under `com.ankurm.redis.`, and
|
|
[07-jackson-untrusted-class.txt](output/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
|
|
|
|
- [JsonRedisConfig](../src/main/java/com/ankurm/redis/config/JsonRedisConfig.java)
|
|
- [JacksonSerializerTest](../src/test/java/com/ankurm/redis/JacksonSerializerTest.java)
|
|
- [Spring Data Redis: serializers](https://docs.spring.io/spring-data/redis/reference/redis/template.html#redis:serializer)
|
|
- [Jackson 2 to Jackson 3 migration guide](https://ankurm.com/jackson-3-migration-guide/)
|
|
|
|
[Home](../README.md) | Prev: [String template and two keyspaces](03-string-template-and-two-keyspaces.md) | Next: [@RedisHash](05-redis-hash.md)
|