Skip to content

Commit 63ab6cb

Browse files
committed
Add Predicate to control default type mapping.
This introduces a `Predicate<JavaType>` that can be specified on the `GenericJackson2JsonRedisSerializer` to give the fine-grained control over what types should include type hint info during serialization. Fixes #3306
1 parent 49042fe commit 63ab6cb

4 files changed

Lines changed: 200 additions & 69 deletions

File tree

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

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.io.Serial;
2020
import java.util.Collections;
2121
import java.util.function.Consumer;
22+
import java.util.function.Predicate;
2223
import java.util.function.Supplier;
2324

2425
import org.jspecify.annotations.Nullable;
@@ -68,6 +69,7 @@
6869
* @author Mao Shuai
6970
* @author John Blum
7071
* @author Anne Lee
72+
* @author Chris Bono
7173
* @see Jackson2ObjectReader
7274
* @see Jackson2ObjectWriter
7375
* @see com.fasterxml.jackson.databind.ObjectMapper
@@ -135,8 +137,7 @@ public GenericJackson2JsonRedisSerializer(@Nullable String typeHintPropertyName,
135137

136138
registerNullValueSerializer(this.mapper, typeHintPropertyName);
137139

138-
this.mapper.setDefaultTyping(createDefaultTypeResolverBuilder(
139-
GenericJackson2JsonRedisSerializerBuilder.DEFAULT_TYPING, getObjectMapper(), typeHintPropertyName));
140+
this.mapper.setDefaultTyping(createDefaultTypeResolverBuilder(null, getObjectMapper(), typeHintPropertyName));
140141
}
141142

142143
/**
@@ -217,7 +218,7 @@ private static Lazy<String> getConfiguredTypeDeserializationPropertyName(ObjectM
217218
});
218219
}
219220

220-
private static StdTypeResolverBuilder createDefaultTypeResolverBuilder(@Nullable DefaultTyping defaultTyping,
221+
private static StdTypeResolverBuilder createDefaultTypeResolverBuilder(@Nullable Predicate<JavaType> defaultTyping,
221222
ObjectMapper objectMapper,
222223
@Nullable String typeHintPropertyName) {
223224

@@ -467,8 +468,6 @@ public void serializeWithType(NullValue value, JsonGenerator jsonGenerator, Seri
467468
*/
468469
public static class GenericJackson2JsonRedisSerializerBuilder {
469470

470-
private static final DefaultTyping DEFAULT_TYPING = DefaultTyping.EVERYTHING;
471-
472471
private @Nullable String typeHintPropertyName;
473472

474473
private Jackson2ObjectReader reader = Jackson2ObjectReader.create();
@@ -479,7 +478,7 @@ public static class GenericJackson2JsonRedisSerializerBuilder {
479478

480479
private @Nullable Boolean defaultTypingEnabled;
481480

482-
private @Nullable DefaultTyping defaultTyping;
481+
private @Nullable Predicate<JavaType> defaultTyping;
483482

484483
private boolean registerNullValueSerializer = true;
485484

@@ -498,7 +497,7 @@ private GenericJackson2JsonRedisSerializerBuilder() {}
498497
*/
499498
public GenericJackson2JsonRedisSerializerBuilder defaultTyping(boolean defaultTyping) {
500499
this.defaultTypingEnabled = defaultTyping;
501-
this.defaultTyping = defaultTyping ? DEFAULT_TYPING : null;
500+
this.defaultTyping = null;
502501
return this;
503502
}
504503

@@ -507,11 +506,11 @@ public GenericJackson2JsonRedisSerializerBuilder defaultTyping(boolean defaultTy
507506
* {@link ObjectMapper#setDefaultTyping(com.fasterxml.jackson.databind.jsontype.TypeResolverBuilder)} for a given
508507
* {@link ObjectMapper}. Default typing is enabled by default if no {@link ObjectMapper} is provided.
509508
*
510-
* @param defaultTyping the default typing mode.
509+
* @param defaultTyping the predicate that matches whether the type should have type info hints added.
511510
* @return this {@link GenericJackson2JsonRedisSerializer.GenericJackson2JsonRedisSerializerBuilder}.
512-
* @since 4.0.3
511+
* @since 4.0.4
513512
*/
514-
public GenericJackson2JsonRedisSerializerBuilder defaultTyping(DefaultTyping defaultTyping) {
513+
public GenericJackson2JsonRedisSerializerBuilder defaultTyping(Predicate<JavaType> defaultTyping) {
515514
this.defaultTypingEnabled = true;
516515
this.defaultTyping = defaultTyping;
517516
return this;
@@ -644,17 +643,17 @@ public GenericJackson2JsonRedisSerializer build() {
644643
*/
645644
private static class TypeResolverBuilder extends ObjectMapper.DefaultTypeResolverBuilder {
646645

647-
private final DefaultTyping typing;
646+
private final @Nullable Predicate<JavaType> defaultTyping;
648647

649-
static TypeResolverBuilder forTyping(@Nullable DefaultTyping defaultTyping, ObjectMapper mapper) {
648+
static TypeResolverBuilder forTyping(@Nullable Predicate<JavaType> defaultTyping, ObjectMapper mapper) {
650649
return new TypeResolverBuilder(
651-
defaultTyping == null ? GenericJackson2JsonRedisSerializerBuilder.DEFAULT_TYPING : defaultTyping,
650+
defaultTyping,
652651
mapper.getPolymorphicTypeValidator());
653652
}
654653

655-
public TypeResolverBuilder(DefaultTyping typing, PolymorphicTypeValidator polymorphicTypeValidator) {
656-
super(typing, polymorphicTypeValidator);
657-
this.typing = typing;
654+
public TypeResolverBuilder(@Nullable Predicate<JavaType> defaultTyping, PolymorphicTypeValidator polymorphicTypeValidator) {
655+
super(DefaultTyping.EVERYTHING, polymorphicTypeValidator);
656+
this.defaultTyping = defaultTyping;
658657
}
659658

660659
@Override
@@ -676,11 +675,7 @@ public boolean useForType(JavaType javaType) {
676675

677676
javaType = resolveArrayOrWrapper(javaType);
678677

679-
if (javaType.isEnumType() && typing != DefaultTyping.EVERYTHING) {
680-
return super.useForType(javaType);
681-
}
682-
683-
if (javaType.isEnumType() || ClassUtils.isPrimitiveOrWrapper(javaType.getRawClass())) {
678+
if (ClassUtils.isPrimitiveOrWrapper(javaType.getRawClass())) {
684679
return false;
685680
}
686681

@@ -689,8 +684,13 @@ public boolean useForType(JavaType javaType) {
689684
return false;
690685
}
691686

692-
if (typing != GenericJackson2JsonRedisSerializerBuilder.DEFAULT_TYPING) {
693-
return super.useForType(javaType);
687+
if (defaultTyping != null) {
688+
return defaultTyping.test(javaType);
689+
}
690+
691+
// Preserve backward compatability if type mapping not set
692+
if (javaType.isEnumType()) {
693+
return false;
694694
}
695695

696696
// [databind#88] Should not apply to JSON tree models:

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

Lines changed: 21 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
import java.io.IOException;
4848
import java.util.Collections;
4949
import java.util.function.Consumer;
50+
import java.util.function.Predicate;
5051
import java.util.function.Supplier;
5152

5253
import org.jspecify.annotations.Nullable;
@@ -68,6 +69,7 @@
6869
* {@link JacksonObjectWriter}.
6970
*
7071
* @author Christoph Strobl
72+
* @author Chris Bono
7173
* @see JacksonObjectReader
7274
* @see JacksonObjectWriter
7375
* @see ObjectMapper
@@ -263,12 +265,11 @@ private static Lazy<String> getConfiguredTypeDeserializationPropertyName(ObjectM
263265
*/
264266
public static class GenericJacksonJsonRedisSerializerBuilder<B extends MapperBuilder<? extends ObjectMapper, ? extends MapperBuilder<?, ?>>> {
265267

266-
private static final DefaultTyping DEFAULT_TYPING = DefaultTyping.NON_FINAL;
267-
268268
private final Supplier<B> builderFactory;
269269

270270
private boolean cacheNullValueSupportEnabled = false;
271-
private @Nullable DefaultTyping defaultTyping = null;
271+
private boolean defaultTypingEnabled;
272+
private @Nullable Predicate<JavaType> defaultTyping;
272273
private @Nullable String typePropertyName;
273274
private PolymorphicTypeValidator typeValidator = BasicPolymorphicTypeValidator.builder()
274275
.allowIfBaseType(Object.class).allowIfSubType((ctx, clazz) -> true).build();
@@ -325,7 +326,7 @@ public GenericJacksonJsonRedisSerializerBuilder<B> enableSpringCacheNullValueSup
325326
@Contract("-> this")
326327
public GenericJacksonJsonRedisSerializerBuilder<B> enableUnsafeDefaultTyping() {
327328

328-
withDefaultTyping();
329+
this.defaultTypingEnabled = true;
329330
return this;
330331
}
331332

@@ -337,15 +338,15 @@ public GenericJacksonJsonRedisSerializerBuilder<B> enableUnsafeDefaultTyping() {
337338
* <strong>WARNING</strong>: without restrictions of the {@link PolymorphicTypeValidator} deserialization is
338339
* vulnerable to arbitrary code execution when reading from untrusted sources.
339340
*
340-
* @param defaultTyping the default typing mode to use.
341+
* @param defaultTyping the predicate that matches whether the type should have type info hints added.
341342
* @return {@code this} builder.
342343
* @since 4.0.3
343344
* @see <a href=
344345
* "https://owasp.org/www-community/vulnerabilities/Deserialization_of_untrusted_data">https://owasp.org/www-community/vulnerabilities/Deserialization_of_untrusted_data</a>
345346
*/
346347
@Contract("_ -> this")
347-
public GenericJacksonJsonRedisSerializerBuilder<B> defaultTyping(DefaultTyping defaultTyping) {
348-
348+
public GenericJacksonJsonRedisSerializerBuilder<B> defaultTyping(Predicate<JavaType> defaultTyping) {
349+
this.defaultTypingEnabled = true;
349350
this.defaultTyping = defaultTyping;
350351
return this;
351352
}
@@ -361,7 +362,7 @@ public GenericJacksonJsonRedisSerializerBuilder<B> defaultTyping(DefaultTyping d
361362
public GenericJacksonJsonRedisSerializerBuilder<B> enableDefaultTyping(PolymorphicTypeValidator typeValidator) {
362363

363364
typeValidator(typeValidator);
364-
withDefaultTyping();
365+
this.defaultTypingEnabled = true;
365366

366367
return this;
367368
}
@@ -395,15 +396,6 @@ public GenericJacksonJsonRedisSerializerBuilder<B> typePropertyName(String typeP
395396
return this;
396397
}
397398

398-
/**
399-
* Enable default typing using {@link DefaultTyping#NON_FINAL} if not already configured.
400-
*/
401-
private void withDefaultTyping() {
402-
if (this.defaultTyping == null) {
403-
defaultTyping(DEFAULT_TYPING);
404-
}
405-
}
406-
407399
/**
408400
* Configures the {@link JacksonObjectWriter}.
409401
*
@@ -472,7 +464,7 @@ public GenericJacksonJsonRedisSerializer build() {
472464
}));
473465
}
474466

475-
if (defaultTyping != null) {
467+
if (defaultTypingEnabled) {
476468

477469
GenericJacksonJsonRedisSerializer.TypeResolverBuilder resolver = new GenericJacksonJsonRedisSerializer.TypeResolverBuilder(
478470
typeValidator, defaultTyping, JsonTypeInfo.As.PROPERTY, JsonTypeInfo.Id.CLASS, typePropertyName);
@@ -628,12 +620,12 @@ public void setupModule(SetupContext context) {
628620

629621
private static class TypeResolverBuilder extends DefaultTypeResolverBuilder {
630622

631-
private final DefaultTyping defaultTyping;
623+
private final @Nullable Predicate<JavaType> defaultTyping;
632624

633-
public TypeResolverBuilder(PolymorphicTypeValidator subtypeValidator, DefaultTyping defaultTyping,
625+
public TypeResolverBuilder(PolymorphicTypeValidator subtypeValidator, @Nullable Predicate<JavaType> defaultTyping,
634626
JsonTypeInfo.As includeAs,
635627
JsonTypeInfo.Id idType, @Nullable String propertyName) {
636-
super(subtypeValidator, defaultTyping, includeAs, idType, propertyName);
628+
super(subtypeValidator, DefaultTyping.NON_FINAL, includeAs, idType, propertyName);
637629
this.defaultTyping = defaultTyping;
638630
}
639631

@@ -656,11 +648,7 @@ public boolean useForType(JavaType javaType) {
656648

657649
javaType = resolveArrayOrWrapper(javaType);
658650

659-
if (javaType.isEnumType() && defaultTyping != GenericJacksonJsonRedisSerializerBuilder.DEFAULT_TYPING) {
660-
return super.useForType(javaType);
661-
}
662-
663-
if (javaType.isEnumType() || ClassUtils.isPrimitiveOrWrapper(javaType.getRawClass())) {
651+
if (ClassUtils.isPrimitiveOrWrapper(javaType.getRawClass())) {
664652
return false;
665653
}
666654

@@ -669,8 +657,13 @@ public boolean useForType(JavaType javaType) {
669657
return false;
670658
}
671659

672-
if (defaultTyping != GenericJacksonJsonRedisSerializerBuilder.DEFAULT_TYPING) {
673-
return super.useForType(javaType);
660+
if (defaultTyping != null) {
661+
return defaultTyping.test(javaType);
662+
}
663+
664+
// Preserve backward compatability if type mapping not set
665+
if (javaType.isEnumType()) {
666+
return false;
674667
}
675668

676669
// [databind#88] Should not apply to JSON tree models:

0 commit comments

Comments
 (0)