diff --git a/src/main/java/org/springframework/data/redis/serializer/GenericJacksonJsonRedisSerializer.java b/src/main/java/org/springframework/data/redis/serializer/GenericJacksonJsonRedisSerializer.java index e486a3f7a5..a1e032c335 100644 --- a/src/main/java/org/springframework/data/redis/serializer/GenericJacksonJsonRedisSerializer.java +++ b/src/main/java/org/springframework/data/redis/serializer/GenericJacksonJsonRedisSerializer.java @@ -596,17 +596,22 @@ public void setupModule(SetupContext context) { private static class TypeResolverBuilder extends DefaultTypeResolverBuilder { + private final DefaultTyping defaultTyping; + public TypeResolverBuilder(PolymorphicTypeValidator subtypeValidator, DefaultTyping t, JsonTypeInfo.As includeAs) { super(subtypeValidator, t, includeAs); + this.defaultTyping = t; } public TypeResolverBuilder(PolymorphicTypeValidator subtypeValidator, DefaultTyping t, String propertyName) { super(subtypeValidator, t, propertyName); + this.defaultTyping = t; } public TypeResolverBuilder(PolymorphicTypeValidator subtypeValidator, DefaultTyping t, JsonTypeInfo.As includeAs, JsonTypeInfo.Id idType, @Nullable String propertyName) { super(subtypeValidator, t, includeAs, idType, propertyName); + this.defaultTyping = t; } @Override @@ -628,7 +633,14 @@ public boolean useForType(JavaType javaType) { javaType = resolveArrayOrWrapper(javaType); - if (javaType.isEnumType() || ClassUtils.isPrimitiveOrWrapper(javaType.getRawClass())) { + // Handle enum types according to DefaultTyping configuration + if (javaType.isEnumType()) { + // Respect DefaultTyping.NON_FINAL_AND_ENUMS for enum types + // For other DefaultTyping options, exclude enums (backward compatible) + return defaultTyping == DefaultTyping.NON_FINAL_AND_ENUMS; + } + + if (ClassUtils.isPrimitiveOrWrapper(javaType.getRawClass())) { return false; } diff --git a/src/test/java/org/springframework/data/redis/serializer/GenericJacksonJsonRedisSerializerUnitTests.java b/src/test/java/org/springframework/data/redis/serializer/GenericJacksonJsonRedisSerializerUnitTests.java index a3cec6c3d4..9f5619d1f2 100644 --- a/src/test/java/org/springframework/data/redis/serializer/GenericJacksonJsonRedisSerializerUnitTests.java +++ b/src/test/java/org/springframework/data/redis/serializer/GenericJacksonJsonRedisSerializerUnitTests.java @@ -403,6 +403,78 @@ void configuresJsonMapper() { assertThat(serializer).isNotNull(); } + @Test // GH-3306 + void shouldSerializeEnumWithTypeMetadataWhenUsingNonFinalAndEnumsTyping() { + + // Create serializer with NON_FINAL_AND_ENUMS to include enum type metadata + GenericJacksonJsonRedisSerializer serializer = GenericJacksonJsonRedisSerializer.create(b -> { + b.customize(mb -> mb.activateDefaultTypingAsProperty( + BasicPolymorphicTypeValidator.builder().allowIfBaseType(Object.class).build(), + DefaultTyping.NON_FINAL_AND_ENUMS, + "@class" + )); + }); + + // Serialize enum + byte[] serialized = serializer.serialize(EnumType.ONE); + assertThat(serialized).isNotNull(); + + // Should include type metadata in JSON (in wrapper array format for NON_FINAL_AND_ENUMS) + // The JSON format is: ["fully.qualified.EnumType", "ONE"] + String json = new String(serialized, StandardCharsets.UTF_8); + assertThat(json).contains("EnumType") // Class name included + .contains("ONE"); // Enum value included + + // Deserialize should correctly restore the enum + Object deserialized = serializer.deserialize(serialized); + assertThat(deserialized).isInstanceOf(EnumType.class).isEqualTo(EnumType.ONE); + } + + @Test // GH-3306 + void shouldDeserializeEnumWithoutTypeMetadataUsingNonFinalTyping() { + + // Create serializer with NON_FINAL (default) which excludes enums + GenericJacksonJsonRedisSerializer serializer = GenericJacksonJsonRedisSerializer.create(b -> { + b.customize(mb -> mb.activateDefaultTypingAsProperty( + BasicPolymorphicTypeValidator.builder().allowIfBaseType(Object.class).build(), + DefaultTyping.NON_FINAL, + "@class" + )); + }); + + // Serialize enum - should not include type metadata + byte[] serialized = serializer.serialize(EnumType.TWO); + assertThat(serialized).isNotNull(); + + // Should NOT include type metadata for enums with NON_FINAL + String json = new String(serialized, StandardCharsets.UTF_8); + assertThat(json).doesNotContain("@class"); + } + + @Test // GH-3306 + void shouldHandleEnumWithUnsafeDefaultTyping_StillConvertsToStringDueToLegacyBehavior() { + + // IMPORTANT: This test demonstrates the ORIGINAL BUG (issue #3306) + // With unsafe default typing, enums are NOT serialized with type metadata + // even though the user might expect them to be preserved as enums. + // + // This is the behavior users complained about in issue #3306. + // Our fix with NON_FINAL_AND_ENUMS allows users to enable enum type metadata + // when they explicitly request it. + + GenericJacksonJsonRedisSerializer serializer = GenericJacksonJsonRedisSerializer + .create(it -> it.enableSpringCacheNullValueSupport().enableUnsafeDefaultTyping()); + + // Serialize enum with unsafe typing + byte[] serialized = serializer.serialize(EnumType.ONE); + assertThat(serialized).isNotNull(); + + // ISSUE: Without type metadata, the enum deserializes as a string "ONE" + // This is what issue #3306 was reporting + Object deserialized = serializer.deserialize(serialized); + assertThat(deserialized).isInstanceOf(String.class); + } + private static void serializeAndDeserializeNullValue(GenericJacksonJsonRedisSerializer serializer) { NullValue nv = BeanUtils.instantiateClass(NullValue.class);