Serialized result of java Class & Record is different with RedisTemplate
See original GitHub issueDescribe the bug
Serialized result of java Class
& Record
is different with RedisTemplate
.
Version information 2.13.3
To Reproduce
====== RedisConfiguration ======
@Configuration
@AutoConfigureAfter(RedisAutoConfiguration.class)
@ConditionalOnClass(RedisOperations.class)
public class RedisConfiguration {
@Bean
public RedisTemplate<String, ?> jacksonRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
final RedisSerializer<String> stringRedisSerializer = new StringRedisSerializer();
final RedisTemplate<String, ?> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
final GenericJackson2JsonRedisSerializer jackson2JsonRedisSerializer = genericJackson2JsonRedisSerializer();
template.setKeySerializer(stringRedisSerializer);
template.setValueSerializer(jackson2JsonRedisSerializer);
template.setHashKeySerializer(stringRedisSerializer);
template.setHashValueSerializer(jackson2JsonRedisSerializer);
return template;
}
@Bean
public GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer() {
final ObjectMapper mapper = new ObjectMapper()
.registerModule(new ParameterNamesModule(JsonCreator.Mode.DEFAULT))
.registerModule(new Jdk8Module())
.registerModule(new JavaTimeModule())
.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.activateDefaultTyping(mapper.getPolymorphicTypeValidator(),
DefaultTyping.NON_FINAL,
As.PROPERTY);
return new GenericJackson2JsonRedisSerializer(mapper);
}
}
====== RequestDto ======
@Data
@NoArgsConstructor
@AllArgsConstructor
public class RequestDto {
private Long id;
private String name;
private OffsetDateTime createdAt;
}
====== RequestRecord ======
public record RequestRecord(Long id,
String name,
OffsetDateTime createdAt) {
}
====== Test Code ======
@SpringBootTest
class RedisRecordTest {
@Autowired
private RedisTemplate<String, RequestRecord> redisRecordTemplate;
@Autowired
private RedisTemplate<String, RequestDto> redisDtoTemplate;
@Autowired
private RedisTemplate<String, String> redisStringTemplate;
@Autowired
private RedisTemplate<String, Object> redisObjectTemplate;
@Autowired
private ObjectMapper mapper;
@DisplayName("record -> String -> set() -> get() -> String -> record -> O")
@Test
void test() throws JsonProcessingException {
final RequestRecord record = new RequestRecord(1L, "doljae", OffsetDateTime.now());
final String serialized = mapper.writeValueAsString(record);
System.out.println(serialized);
redisStringTemplate.opsForValue().set("key", serialized);
final String fromRedis = redisStringTemplate.opsForValue().get("key");
System.out.println(fromRedis);
final RequestRecord deserialized = mapper.readValue(fromRedis, RequestRecord.class);
System.out.println(deserialized);
}
@DisplayName("Class -> set() -> get() -> Class -> O")
@Test
void test2() {
final RequestDto dto = new RequestDto(1L, "doljae", OffsetDateTime.now());
redisDtoTemplate.opsForValue().set("key", dto);
/*
redis-cli
get key
-> "{\"@class\":\"com.example.redis.RequestDto\",\"id\":1,\"name\":\"doljae\",\"createdA\":\"2022-06-08T21:39:01.166705+09:00\"}"
*/
final RequestDto fromRedis = redisDtoTemplate.opsForValue().get("key");
System.out.println(fromRedis);
}
@DisplayName("record -> set() -> get() -> record -> X")
@Test
void test3() {
final RequestRecord record = new RequestRecord(1L, "seokjae", OffsetDateTime.now());
redisRecordTemplate.opsForValue().set("key", record);
/*
redis-cli
get key
-> "{\"id\":1,\"name\":\"doljae\",\"createdAt\":\"2022-06-08T21:40:36.057138+09:00\"}"
*/
// Caused by: com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Could not resolve subtype of [simple type, class java.lang.Object]: missing type id property '@class'
// at [Source: (byte[])"{"id":1,"name":"doljae","createdAt":"2022-06-08T21:40:36.057138+09:00"}"; line: 1, column: 72]
final RequestRecord deserialized = redisRecordTemplate.opsForValue().get("key");
}
}
Expected behavior Both class and record objects should be stored and deserialized normally in RedisTemplate.
Additional context
-
The issue occurred while using
Spring Data Redis
class, but sinceObjectMapper
does serialization and deserialization, I reported it to Jackson issue borad. -
You can reproduce this issue with this repository’s test class (use redis docker image in
repo/docker/docker-compose.yml
) -
I found that if i adjust ObjectMapper Configuration, both Class & Record serialize & deserialize normally.
mapper.activateDefaultTyping(mapper.getPolymorphicTypeValidator(),
DefaultTyping.EVERYTHING,
As.PROPERTY);
- but this way exposes every meta data of object’s fields…
Issue Analytics
- State:
- Created a year ago
- Comments:10 (4 by maintainers)
Top Results From Across the Web
How to Serialize to Java Object in Spring Data Redis
This is my redis Template that i have defined in my @configuration class, it's taking a jedis Connection factory, i have set my...
Read more >Spring Data Redis
RedisTemplate uses a Java-based serializer for most of its operations. This means that any object written or read by the template is serialized...
Read more >Simpler object and data serialization using Java records
Serialization with records ... With Java Serialization, a record class is made serializable just like a normal class, simply by implementing java.
Read more >Introduction to Spring Data Redis - Baeldung
There is a number of Redis client implementations available for Java. In this tutorial, we'll use Jedis — a simple and powerful Redis...
Read more >How to use Redis-Template in Java Spring Boot? - Medium
Spring encapsulates a more powerful template, redisTemplate, ... The same operation as leftPush , the only difference is that the value of ...
Read more >Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start FreeTop Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
Top GitHub Comments
@doljae getting the ObjectMapper configuration is something that Spring Data Redis users or maintainers can help with.
I think your problem can be divided in two parts:
But one other thing I would STRONGLY recommend: instead of attempting to serialize any given “Root value” directly, you really should instead use a wrapper if possible: something like:
This will avoid many pitfalls, including problem of Java Type Erasure for the root value. It may also remove the need to active default typing completely (although this depends a little bit on kind of data you serialize).
In your case, for example, one problem is that
Record
types arefinal
and non-polymorphic, and as such inclusion of type id may not occur as you’d expect, if such value is serialized directly.i can reproduce your issue with the new test case, and yes it is because of
DefaultTyping.NON_FINAL
. Changing it toDefaultTyping.EVERYTHING
resolves this. However it is somewhat problematic because it also adds default typing to unrelated places like the Long field.A better alternative is to use
mapper.writerFor(Object.class).writeValueAsBytes(record)
. This adds the type information even in the NON_FINAL case. However I can’t say how to make spring use this approach.