Skip to content

Commit 17820d1

Browse files
committed
Allow users to customize the internal ObjectMapper created by GenericJackson2JsonRedisSerializer.
We now allow the internally created Jackson ObjectMapper to be customized and further configured after construction of the GenericJackson2JsonRedisSerializer when a user does not explicitly provide a custom ObjectMapper during construction. Even when providing a custom ObjectMapper, not all configuration applied by the GenericJackson2JsonRedisSerialzier (such as (standard) type resolution) to the internal ObjectMapper would get applied to the user-provided ObjectMapper as well. Closes spring-projects#2601
1 parent 46279d8 commit 17820d1

File tree

2 files changed

+91
-16
lines changed

2 files changed

+91
-16
lines changed

src/main/java/org/springframework/data/redis/serializer/GenericJackson2JsonRedisSerializer.java

+39-4
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import java.io.IOException;
1919
import java.util.Collections;
20+
import java.util.function.Consumer;
2021
import java.util.function.Supplier;
2122

2223
import org.springframework.cache.support.NullValue;
@@ -54,6 +55,7 @@
5455
* @author Christoph Strobl
5556
* @author Mark Paluch
5657
* @author Mao Shuai
58+
* @author John Blum
5759
* @since 1.6
5860
*/
5961
public class GenericJackson2JsonRedisSerializer implements RedisSerializer<Object> {
@@ -197,6 +199,16 @@ public static void registerNullValueSerializer(ObjectMapper objectMapper, @Nulla
197199
objectMapper.registerModule(new SimpleModule().addSerializer(new NullValueSerializer(classPropertyTypeName)));
198200
}
199201

202+
/**
203+
* Gets the configured {@link ObjectMapper} used internally by this {@link GenericJackson2JsonRedisSerializer}
204+
* to de/serialize {@link Object objects} as {@literal JSON}.
205+
*
206+
* @return the configured {@link ObjectMapper}.
207+
*/
208+
protected ObjectMapper getObjectMapper() {
209+
return this.mapper;
210+
}
211+
200212
@Override
201213
public byte[] serialize(@Nullable Object source) throws SerializationException {
202214

@@ -206,8 +218,9 @@ public byte[] serialize(@Nullable Object source) throws SerializationException {
206218

207219
try {
208220
return writer.write(mapper, source);
209-
} catch (IOException e) {
210-
throw new SerializationException("Could not write JSON: " + e.getMessage(), e);
221+
} catch (IOException cause) {
222+
String message = String.format("Could not write JSON: %s", cause.getMessage());
223+
throw new SerializationException(message, cause);
211224
}
212225
}
213226

@@ -235,11 +248,33 @@ public <T> T deserialize(@Nullable byte[] source, Class<T> type) throws Serializ
235248

236249
try {
237250
return (T) reader.read(mapper, source, resolveType(source, type));
238-
} catch (Exception ex) {
239-
throw new SerializationException("Could not read JSON: " + ex.getMessage(), ex);
251+
} catch (Exception cause) {
252+
String message = String.format("Could not read JSON:%s ", cause.getMessage());
253+
throw new SerializationException(message, cause);
240254
}
241255
}
242256

257+
/**
258+
* Builder method used to configure and customize the internal Jackson {@link ObjectMapper} created by
259+
* this {@link GenericJackson2JsonRedisSerializer} and used to de/serialize {@link Object objects}
260+
* as {@literal JSON}.
261+
*
262+
* @param objectMapperConfigurer {@link Consumer} used to configure and customize the internal {@link ObjectMapper};
263+
* must not be {@literal null}.
264+
* @return this {@link GenericJackson2JsonRedisSerializer}.
265+
* @throws IllegalArgumentException if the {@link Consumer} used to configure and customize
266+
* the internal {@link ObjectMapper} is {@literal null}.
267+
*/
268+
public GenericJackson2JsonRedisSerializer configure(Consumer<ObjectMapper> objectMapperConfigurer) {
269+
270+
Assert.notNull(objectMapperConfigurer,
271+
"Consumer used to configure and customize ObjectMapper must not be null");
272+
273+
objectMapperConfigurer.accept(getObjectMapper());
274+
275+
return this;
276+
}
277+
243278
protected JavaType resolveType(byte[] source, Class<?> type) throws IOException {
244279

245280
if (!type.equals(Object.class) || !defaultTypingEnabled.get()) {

src/test/java/org/springframework/data/redis/serializer/GenericJackson2JsonRedisSerializerUnitTests.java

+52-12
Original file line numberDiff line numberDiff line change
@@ -15,29 +15,35 @@
1515
*/
1616
package org.springframework.data.redis.serializer;
1717

18-
import static org.assertj.core.api.Assertions.*;
19-
import static org.mockito.Mockito.*;
20-
import static org.springframework.test.util.ReflectionTestUtils.*;
21-
import static org.springframework.util.ObjectUtils.*;
22-
23-
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
24-
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
25-
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
26-
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
27-
import lombok.Data;
28-
import lombok.ToString;
18+
import static org.assertj.core.api.Assertions.assertThat;
19+
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
20+
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
21+
import static org.mockito.ArgumentMatchers.eq;
22+
import static org.mockito.Mockito.any;
23+
import static org.mockito.Mockito.mock;
24+
import static org.mockito.Mockito.times;
25+
import static org.mockito.Mockito.verify;
26+
import static org.mockito.Mockito.verifyNoInteractions;
27+
import static org.mockito.Mockito.verifyNoMoreInteractions;
28+
import static org.mockito.Mockito.when;
29+
import static org.springframework.test.util.ReflectionTestUtils.getField;
30+
import static org.springframework.util.ObjectUtils.nullSafeEquals;
31+
import static org.springframework.util.ObjectUtils.nullSafeHashCode;
2932

3033
import java.io.IOException;
3134
import java.nio.charset.StandardCharsets;
3235
import java.time.LocalDate;
3336
import java.util.Map;
3437
import java.util.UUID;
3538
import java.util.concurrent.atomic.AtomicReference;
39+
import java.util.function.Consumer;
3640

3741
import org.junit.jupiter.api.Test;
3842
import org.mockito.Mockito;
43+
3944
import org.springframework.beans.BeanUtils;
4045
import org.springframework.cache.support.NullValue;
46+
import org.springframework.lang.Nullable;
4147

4248
import com.fasterxml.jackson.annotation.JsonTypeInfo;
4349
import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
@@ -47,15 +53,22 @@
4753
import com.fasterxml.jackson.databind.JsonMappingException;
4854
import com.fasterxml.jackson.databind.ObjectMapper;
4955
import com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping;
56+
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
57+
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
5058
import com.fasterxml.jackson.databind.jsontype.TypeResolverBuilder;
5159
import com.fasterxml.jackson.databind.type.TypeFactory;
52-
import org.springframework.lang.Nullable;
60+
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
61+
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
62+
63+
import lombok.Data;
64+
import lombok.ToString;
5365

5466
/**
5567
* Unit tests for {@link GenericJackson2JsonRedisSerializer}.
5668
*
5769
* @author Christoph Strobl
5870
* @author Mark Paluch
71+
* @author John Blum
5972
*/
6073
class GenericJackson2JsonRedisSerializerUnitTests {
6174

@@ -407,6 +420,33 @@ void deserializesJavaTimeFrimBytes() {
407420
assertThat(serializer.deserialize(source, WithJsr310.class).myDate).isEqualTo(java.time.LocalDate.of(2022,9,2));
408421
}
409422

423+
@Test // GH-2601
424+
public void internalObjectMapperCustomization() {
425+
426+
GenericJackson2JsonRedisSerializer serializer = new GenericJackson2JsonRedisSerializer();
427+
428+
com.fasterxml.jackson.databind.Module mockModule = mock(com.fasterxml.jackson.databind.Module.class);
429+
430+
ObjectMapper mockObjectMapper = mock(ObjectMapper.class);
431+
432+
Consumer<ObjectMapper> configurer = objectMapper -> mockObjectMapper.registerModule(mockModule);
433+
434+
assertThat(serializer.configure(configurer)).isSameAs(serializer);
435+
436+
verify(mockObjectMapper, times(1)).registerModule(eq(mockModule));
437+
verifyNoMoreInteractions(mockObjectMapper);
438+
verifyNoInteractions(mockModule);
439+
}
440+
441+
@Test // GH-2601
442+
public void configureWithNullConsumerThrowsIllegalArgumentException() {
443+
444+
assertThatIllegalArgumentException()
445+
.isThrownBy(() -> new GenericJackson2JsonRedisSerializer().configure(null))
446+
.withMessage("Consumer used to configure and customize ObjectMapper must not be null")
447+
.withNoCause();
448+
}
449+
410450
private static void serializeAndDeserializeNullValue(GenericJackson2JsonRedisSerializer serializer) {
411451

412452
NullValue nv = BeanUtils.instantiateClass(NullValue.class);

0 commit comments

Comments
 (0)