-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Description
When using GenericJacksonJsonRedisSerializer, enum values are always serialized without type metadata, even when DefaultTyping.NON_FINAL_AND_ENUMS is explicitly configured. This causes ClassCastException during deserialization.
Example
public enum Status {
ACTIVE, INACTIVE
}
@Cacheable(cacheNames = "status", cacheManager = "redisCacheManager")
public Status getStatus(String id) {
return Status.ACTIVE;
}Serialized value in Redis:
"ACTIVE"Exception on deserialization:
java.lang.ClassCastException: class java.lang.String cannot be cast to class Status
Configuration Attempt
GenericJacksonJsonRedisSerializer.create(b -> {
b.enableDefaultTyping(ptv);
b.customize(mb ->
mb.activateDefaultTypingAsProperty(
ptv,
DefaultTyping.NON_FINAL_AND_ENUMS,
"@class"
)
);
});Using customize() to override with Jackson's default DefaultTypeResolverBuilder does enable enum type metadata, but it loses Spring's Kotlin-specific handling for final classes:
// This Kotlin support in Spring's TypeResolverBuilder is lost:
if (javaType.isFinal() && !KotlinDetector.isKotlinType(javaType.getRawClass())
&& javaType.getRawClass().getPackageName().startsWith("java")) {
return false;
}This forces users to choose between enum support and Kotlin data class support.
Root Cause
Spring's TypeResolverBuilder unconditionally excludes enum types regardless of the DefaultTyping setting:
if (javaType.isEnumType() || ClassUtils.isPrimitiveOrWrapper(javaType.getRawClass())) {
return false; // Always excluded
}Suggested Fix
Store the DefaultTyping parameter and use it in useForType():
private static class TypeResolverBuilder extends DefaultTypeResolverBuilder {
private final DefaultTyping defaultTyping;
public TypeResolverBuilder(PolymorphicTypeValidator subtypeValidator, DefaultTyping defaultTyping,
JsonTypeInfo.As includeAs, JsonTypeInfo.Id idType, @Nullable String propertyName) {
super(subtypeValidator, defaultTyping, includeAs, idType, propertyName);
this.defaultTyping = defaultTyping;
}
@Override
public boolean useForType(JavaType javaType) {
if (javaType.isJavaLangObject()) {
return true;
}
javaType = resolveArrayOrWrapper(javaType);
// Respect DefaultTyping.NON_FINAL_AND_ENUMS for enum types
if (javaType.isEnumType()) {
return defaultTyping == DefaultTyping.NON_FINAL_AND_ENUMS;
}
if (ClassUtils.isPrimitiveOrWrapper(javaType.getRawClass())) {
return false;
}
if (javaType.isFinal() && !KotlinDetector.isKotlinType(javaType.getRawClass())
&& javaType.getRawClass().getPackageName().startsWith("java")) {
return false;
}
return !TreeNode.class.isAssignableFrom(javaType.getRawClass());
}
// ... resolveArrayOrWrapper() unchanged
}This preserves backward compatibility (enums excluded by default with NON_FINAL) while allowing enum type metadata when explicitly requested via NON_FINAL_AND_ENUMS.
This preserves backward compatibility while allowing enum type metadata when explicitly requested.
Current Workarounds
- Wrap enums in a generic container class
- Copy and modify Spring's
TypeResolverBuilder(not maintainable)
Environment
- Spring Data Redis: 4.0.x
- Jackson: 3.x