diff --git a/src/main/java/org/springframework/data/redis/serializer/GenericJackson2JsonRedisSerializer.java b/src/main/java/org/springframework/data/redis/serializer/GenericJackson2JsonRedisSerializer.java index 28b9474b2..6befb31e3 100644 --- a/src/main/java/org/springframework/data/redis/serializer/GenericJackson2JsonRedisSerializer.java +++ b/src/main/java/org/springframework/data/redis/serializer/GenericJackson2JsonRedisSerializer.java @@ -134,6 +134,17 @@ public class GenericJackson2JsonRedisSerializer implements RedisSerializer nullValueSerializer; + + private GenericJackson2JsonRedisSerializerBuilder( + ObjectMapper objectMapper, + JacksonObjectReader reader, + JacksonObjectWriter writer + ) { + this.mapper = objectMapper; + this.reader = reader; + this.writer = writer; + } + + /** + * Configure a classPropertyName. + * + * @param classPropertyTypeName can be {@literal null}. + * @return this {@link GenericJackson2JsonRedisSerializer.GenericJackson2JsonRedisSerializerBuilder}. + * @since 3.3 + */ + public GenericJackson2JsonRedisSerializerBuilder classPropertyTypeName(@Nullable String classPropertyTypeName) { + this.classPropertyTypeName = classPropertyTypeName; + return this; + } + + /** + * Register a nullValueSerializer. + * + * @param nullValueSerializer the {@link StdSerializer} to use for {@link NullValue} serialization. Can be {@literal null}. + * @return this {@link GenericJackson2JsonRedisSerializer.GenericJackson2JsonRedisSerializerBuilder}. + */ + public GenericJackson2JsonRedisSerializerBuilder registerNullValueSerializer(@Nullable StdSerializer nullValueSerializer) { + this.nullValueSerializer = nullValueSerializer; + return this; + } + + /** + * Create new instance of {@link GenericJackson2JsonRedisSerializer} with configuration options applied. + * + * @return new instance of {@link GenericJackson2JsonRedisSerializer}. + */ + public GenericJackson2JsonRedisSerializer build() { + Assert.notNull(this.mapper, "ObjectMapper must not be null"); + Assert.notNull(this.reader, "Reader must not be null"); + Assert.notNull(this.writer, "Writer must not be null"); + + this.mapper.registerModule(new SimpleModule().addSerializer(this.nullValueSerializer != null ? this.nullValueSerializer : new NullValueSerializer(this.classPropertyTypeName))); + return new GenericJackson2JsonRedisSerializer(this.mapper, this.reader, this.writer, this.classPropertyTypeName); + } + } + /** * Custom {@link StdTypeResolverBuilder} that considers typing for non-primitive types. Primitives, their wrappers and * primitive arrays do not require type hints. The default {@code DefaultTyping#EVERYTHING} typing does not satisfy diff --git a/src/test/java/org/springframework/data/redis/serializer/GenericJackson2JsonRedisSerializerUnitTests.java b/src/test/java/org/springframework/data/redis/serializer/GenericJackson2JsonRedisSerializerUnitTests.java index 7f54a324c..effbeae47 100644 --- a/src/test/java/org/springframework/data/redis/serializer/GenericJackson2JsonRedisSerializerUnitTests.java +++ b/src/test/java/org/springframework/data/redis/serializer/GenericJackson2JsonRedisSerializerUnitTests.java @@ -40,6 +40,10 @@ import java.util.UUID; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.jsontype.TypeSerializer; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; import org.junit.jupiter.api.Test; import org.mockito.Mockito; @@ -420,7 +424,7 @@ class GenericJackson2JsonRedisSerializerUnitTests { } @Test // GH-2601 - public void internalObjectMapperCustomization() { + void internalObjectMapperCustomization() { GenericJackson2JsonRedisSerializer serializer = new GenericJackson2JsonRedisSerializer(); @@ -432,13 +436,13 @@ class GenericJackson2JsonRedisSerializerUnitTests { assertThat(serializer.configure(configurer)).isSameAs(serializer); - verify(mockObjectMapper, times(1)).registerModule(eq(mockModule)); + verify(mockObjectMapper, times(1)).registerModule(mockModule); verifyNoMoreInteractions(mockObjectMapper); verifyNoInteractions(mockModule); } @Test // GH-2601 - public void configureWithNullConsumerThrowsIllegalArgumentException() { + void configureWithNullConsumerThrowsIllegalArgumentException() { assertThatIllegalArgumentException() .isThrownBy(() -> new GenericJackson2JsonRedisSerializer().configure(null)) @@ -446,6 +450,49 @@ class GenericJackson2JsonRedisSerializerUnitTests { .withNoCause(); } + @Test + void defaultSerializeAndDeserializeNullValueWithBuilderClass() { + GenericJackson2JsonRedisSerializer serializer = GenericJackson2JsonRedisSerializer.builder( + new ObjectMapper().enableDefaultTyping(DefaultTyping.EVERYTHING, As.PROPERTY), + JacksonObjectReader.create(), JacksonObjectWriter.create()) + .classPropertyTypeName(null) + .registerNullValueSerializer(null) + .build(); + + serializeAndDeserializeNullValue(serializer); + } + + @Test + void customSerializeAndDeserializeNullValueWithBuilderClass() { + GenericJackson2JsonRedisSerializer serializer = GenericJackson2JsonRedisSerializer.builder( + new ObjectMapper(), JacksonObjectReader.create(), JacksonObjectWriter.create()) + .classPropertyTypeName(null) + .registerNullValueSerializer(new StdSerializer<>(NullValue.class) { + @Override + public void serialize(NullValue nullValue, + JsonGenerator jsonGenerator, + SerializerProvider serializerProvider) throws IOException { + jsonGenerator.writeNull(); + } + + @Override + public void serializeWithType(NullValue value, JsonGenerator jsonGenerator, SerializerProvider serializers, + TypeSerializer typeSerializer) throws IOException { + + serialize(value, jsonGenerator, serializers); + } + }) + .build(); + + NullValue nv = BeanUtils.instantiateClass(NullValue.class); + + byte[] serializedValue = serializer.serialize(nv); + assertThat(serializedValue).isNotNull(); + + Object deserializedValue = serializer.deserialize(serializedValue); + assertThat(deserializedValue).isNull(); + } + private static void serializeAndDeserializeNullValue(GenericJackson2JsonRedisSerializer serializer) { NullValue nv = BeanUtils.instantiateClass(NullValue.class);