Skip to content

Commit f3de2d5

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 #2601
1 parent f1492e1 commit f3de2d5

File tree

2 files changed

+71
-4
lines changed

2 files changed

+71
-4
lines changed

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

+37-4
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import java.io.IOException;
1919
import java.io.Serial;
2020
import java.util.Collections;
21+
import java.util.function.Consumer;
2122
import java.util.function.Supplier;
2223

2324
import org.springframework.cache.support.NullValue;
@@ -196,12 +197,22 @@ private Supplier<String> newTypeHintPropertyNameSupplier(ObjectMapper mapper, @N
196197
return typeHintPropertyName != null ? () -> typeHintPropertyName
197198
: Lazy.of(() -> defaultTypingEnabled.get() ? null
198199
: mapper.getDeserializationConfig().getDefaultTyper(null)
199-
.buildTypeDeserializer(mapper.getDeserializationConfig(),
200-
mapper.getTypeFactory().constructType(Object.class), Collections.emptyList())
200+
.buildTypeDeserializer(mapper.getDeserializationConfig(),
201+
mapper.getTypeFactory().constructType(Object.class), Collections.emptyList())
201202
.getPropertyName())
202203
.or("@class");
203204
}
204205

206+
/**
207+
* Gets the configured {@link ObjectMapper} used internally by this {@link GenericJackson2JsonRedisSerializer}
208+
* to de/serialize {@link Object objects} as {@literal JSON}.
209+
*
210+
* @return the configured {@link ObjectMapper}.
211+
*/
212+
protected ObjectMapper getObjectMapper() {
213+
return this.mapper;
214+
}
215+
205216
@Override
206217
public byte[] serialize(@Nullable Object source) throws SerializationException {
207218

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

249260
try {
250261
return (T) reader.read(mapper, source, resolveType(source, type));
251-
} catch (Exception ex) {
252-
throw new SerializationException("Could not read JSON: " + ex.getMessage(), ex);
262+
} catch (Exception cause) {
263+
String message = String.format("Could not read JSON:%s ", cause.getMessage());
264+
throw new SerializationException(message, cause);
253265
}
254266
}
255267

268+
/**
269+
* Builder method used to configure and customize the internal Jackson {@link ObjectMapper} created by
270+
* this {@link GenericJackson2JsonRedisSerializer} and used to de/serialize {@link Object objects}
271+
* as {@literal JSON}.
272+
*
273+
* @param objectMapperConfigurer {@link Consumer} used to configure and customize the internal {@link ObjectMapper};
274+
* must not be {@literal null}.
275+
* @return this {@link GenericJackson2JsonRedisSerializer}.
276+
* @throws IllegalArgumentException if the {@link Consumer} used to configure and customize
277+
* the internal {@link ObjectMapper} is {@literal null}.
278+
*/
279+
public GenericJackson2JsonRedisSerializer configure(Consumer<ObjectMapper> objectMapperConfigurer) {
280+
281+
Assert.notNull(objectMapperConfigurer,
282+
"Consumer used to configure and customize ObjectMapper must not be null");
283+
284+
objectMapperConfigurer.accept(getObjectMapper());
285+
286+
return this;
287+
}
288+
256289
protected JavaType resolveType(byte[] source, Class<?> type) throws IOException {
257290

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

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

+34
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,14 @@
1717

1818
import static org.assertj.core.api.Assertions.assertThat;
1919
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
20+
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
21+
import static org.mockito.ArgumentMatchers.eq;
2022
import static org.mockito.Mockito.any;
2123
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;
2228
import static org.mockito.Mockito.when;
2329
import static org.springframework.test.util.ReflectionTestUtils.getField;
2430
import static org.springframework.util.ObjectUtils.nullSafeEquals;
@@ -32,6 +38,7 @@
3238
import java.util.Objects;
3339
import java.util.UUID;
3440
import java.util.concurrent.atomic.AtomicReference;
41+
import java.util.function.Consumer;
3542

3643
import org.junit.jupiter.api.Test;
3744
import org.mockito.Mockito;
@@ -412,6 +419,33 @@ void deserializesJavaTimeFrimBytes() {
412419
assertThat(serializer.deserialize(source, WithJsr310.class).myDate).isEqualTo(java.time.LocalDate.of(2022,9,2));
413420
}
414421

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

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

0 commit comments

Comments
 (0)