From 4da3bdf79860ba44934dbd0d270012865efd133a Mon Sep 17 00:00:00 2001 From: John Blum Date: Wed, 14 Jun 2023 12:14:59 -0700 Subject: [PATCH] Refactor GenericJackson2JsonRedisSerializer. * Cleanup and simpifly source code. * Fix compiler warnings. * Edit Javadoc. Closes #2609 --- .../GenericJackson2JsonRedisSerializer.java | 197 ++++++++++-------- 1 file changed, 109 insertions(+), 88 deletions(-) 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 c3922d5f7..6b40694c0 100644 --- a/src/main/java/org/springframework/data/redis/serializer/GenericJackson2JsonRedisSerializer.java +++ b/src/main/java/org/springframework/data/redis/serializer/GenericJackson2JsonRedisSerializer.java @@ -16,11 +16,13 @@ package org.springframework.data.redis.serializer; import java.io.IOException; +import java.io.Serial; import java.util.Collections; import java.util.function.Supplier; import org.springframework.cache.support.NullValue; import org.springframework.core.KotlinDetector; +import org.springframework.data.redis.util.RedisAssertions; import org.springframework.data.util.Lazy; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -46,19 +48,38 @@ import com.fasterxml.jackson.databind.ser.std.StdSerializer; import com.fasterxml.jackson.databind.type.TypeFactory; /** - * Generic Jackson 2-based {@link RedisSerializer} that maps {@link Object objects} to JSON using dynamic typing. + * Generic Jackson 2-based {@link RedisSerializer} that maps {@link Object objects} to and from {@literal JSON} + * using dynamic typing. *

- * JSON reading and writing can be customized by configuring {@link JacksonObjectReader} respective - * {@link JacksonObjectWriter}. + * {@literal JSON} reading and writing can be customized by configuring a {@link JacksonObjectReader} + * and {@link JacksonObjectWriter}. * * @author Christoph Strobl * @author Mark Paluch * @author Mao Shuai + * @author John Blum + * @see org.springframework.data.redis.serializer.JacksonObjectReader + * @see org.springframework.data.redis.serializer.JacksonObjectWriter + * @see com.fasterxml.jackson.databind.ObjectMapper * @since 1.6 */ public class GenericJackson2JsonRedisSerializer implements RedisSerializer { - private final ObjectMapper mapper; + /** + * Register {@link NullValueSerializer} in the given {@link ObjectMapper} with an optional + * {@code classPropertyTypeName}. This method should be called by code that customizes + * {@link GenericJackson2JsonRedisSerializer} by providing an external {@link ObjectMapper}. + * + * @param objectMapper the object mapper to customize. + * @param classPropertyTypeName name of the type property. Defaults to {@code @class} if {@literal null}/empty. + * @since 2.2 + */ + public static void registerNullValueSerializer(ObjectMapper objectMapper, @Nullable String classPropertyTypeName) { + + // Simply setting {@code mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)} does not help here + // since we need the type hint embedded for deserialization using the default typing feature. + objectMapper.registerModule(new SimpleModule().addSerializer(new NullValueSerializer(classPropertyTypeName))); + } private final JacksonObjectReader reader; @@ -66,21 +87,27 @@ public class GenericJackson2JsonRedisSerializer implements RedisSerializer defaultTypingEnabled; + private final ObjectMapper mapper; + private final TypeResolver typeResolver; /** - * Creates {@link GenericJackson2JsonRedisSerializer} and configures {@link ObjectMapper} for default typing. + * Creates {@link GenericJackson2JsonRedisSerializer} initialized with an {@link ObjectMapper} configured for + * default typing. */ public GenericJackson2JsonRedisSerializer() { this((String) null); } /** - * Creates {@link GenericJackson2JsonRedisSerializer} and configures {@link ObjectMapper} for default typing using the - * given {@literal name}. In case of an {@literal empty} or {@literal null} String the default - * {@link JsonTypeInfo.Id#CLASS} will be used. + * Creates {@link GenericJackson2JsonRedisSerializer} initialized with an {@link ObjectMapper} configured for + * default typing using the given {@link String name}. + *

+ * In case {@link String name} is {@literal empty} or {@literal null}, then {@link JsonTypeInfo.Id#CLASS} + * will be used. * - * @param classPropertyTypeName name of the JSON property holding type information. Can be {@literal null}. + * @param classPropertyTypeName {@link String name} of the JSON property holding type information; + * can be {@literal null}. * @see ObjectMapper#activateDefaultTypingAsProperty(PolymorphicTypeValidator, DefaultTyping, String) * @see ObjectMapper#activateDefaultTyping(PolymorphicTypeValidator, DefaultTyping, As) */ @@ -89,13 +116,17 @@ public class GenericJackson2JsonRedisSerializer implements RedisSerializer + * In case {@link String name} is {@literal empty} or {@literal null}, then {@link JsonTypeInfo.Id#CLASS} + * will be used. * - * @param classPropertyTypeName name of the JSON property holding type information. Can be {@literal null}. - * @param reader the {@link JacksonObjectReader} function to read objects using {@link ObjectMapper}. - * @param writer the {@link JacksonObjectWriter} function to write objects using {@link ObjectMapper}. + * @param classPropertyTypeName {@link String name} of the JSON property holding type information; + * can be {@literal null}. + * @param reader {@link JacksonObjectReader} function to read objects using {@link ObjectMapper}. + * @param writer {@link JacksonObjectWriter} function to write objects using {@link ObjectMapper}. * @see ObjectMapper#activateDefaultTypingAsProperty(PolymorphicTypeValidator, DefaultTyping, String) * @see ObjectMapper#activateDefaultTyping(PolymorphicTypeValidator, DefaultTyping, As) * @since 3.0 @@ -105,19 +136,17 @@ public class GenericJackson2JsonRedisSerializer implements RedisSerializer mapper.getSerializationConfig() + .getDefaultTyper(null) != null); - this.defaultTypingEnabled = Lazy.of(() -> mapper.getSerializationConfig().getDefaultTyper(null) != null); - - Supplier typeHintPropertyNameSupplier; - - if (typeHintPropertyName == null) { - - typeHintPropertyNameSupplier = Lazy.of(() -> { - if (defaultTypingEnabled.get()) { - return null; - } - - return mapper.getDeserializationConfig().getDefaultTyper(null) - .buildTypeDeserializer(mapper.getDeserializationConfig(), - mapper.getTypeFactory().constructType(Object.class), Collections.emptyList()) - .getPropertyName(); - - }).or("@class"); - } else { - typeHintPropertyNameSupplier = () -> typeHintPropertyName; - } - - this.typeResolver = new TypeResolver(Lazy.of(mapper::getTypeFactory), typeHintPropertyNameSupplier); + this.typeResolver = new TypeResolver(Lazy.of(mapper::getTypeFactory), + newTypeHintPropertyNameSupplier(mapper, typeHintPropertyName, this.defaultTypingEnabled)); } - /** - * Register {@link NullValueSerializer} in the given {@link ObjectMapper} with an optional - * {@code classPropertyTypeName}. This method should be called by code that customizes - * {@link GenericJackson2JsonRedisSerializer} by providing an external {@link ObjectMapper}. - * - * @param objectMapper the object mapper to customize. - * @param classPropertyTypeName name of the type property. Defaults to {@code @class} if {@literal null}/empty. - * @since 2.2 - */ - public static void registerNullValueSerializer(ObjectMapper objectMapper, @Nullable String classPropertyTypeName) { + private Supplier newTypeHintPropertyNameSupplier(ObjectMapper mapper, @Nullable String typeHintPropertyName, + Lazy defaultTypingEnabled) { - // simply setting {@code mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)} does not help here since we need - // the type hint embedded for deserialization using the default typing feature. - objectMapper.registerModule(new SimpleModule().addSerializer(new NullValueSerializer(classPropertyTypeName))); + return typeHintPropertyName != null ? () -> typeHintPropertyName + : Lazy.of(() -> defaultTypingEnabled.get() ? null + : mapper.getDeserializationConfig().getDefaultTyper(null) + .buildTypeDeserializer(mapper.getDeserializationConfig(), + mapper.getTypeFactory().constructType(Object.class), Collections.emptyList()) + .getPropertyName()) + .or("@class"); } @Override @@ -206,8 +211,9 @@ public class GenericJackson2JsonRedisSerializer implements RedisSerializer T deserialize(@Nullable byte[] source, Class type) throws SerializationException { - Assert.notNull(type, - "Deserialization type must not be null Please provide Object.class to make use of Jackson2 default typing."); + Assert.notNull(type, "Deserialization type must not be null;" + + " Please provide Object.class to make use of Jackson2 default typing."); if (SerializationUtils.isEmpty(source)) { return null; @@ -292,7 +305,9 @@ public class GenericJackson2JsonRedisSerializer implements RedisSerializer { + @Serial private static final long serialVersionUID = 1999052150548658808L; + private final String classIdentifier; /** @@ -305,17 +320,19 @@ public class GenericJackson2JsonRedisSerializer implements RedisSerializer