Skip to content

Commit fb140e3

Browse files
committed
Polishing.
Revise builder. Accept builder components in builder methods instead of the builder factory method. Enforce valid parameters instead of lenient, potentially null parameters. Introduce configuration means to control default typing. Extend tests. See #2878 Original pull request: #2905
1 parent 6b56cfb commit fb140e3

File tree

2 files changed

+213
-125
lines changed

2 files changed

+213
-125
lines changed

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

Lines changed: 165 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
* @author Mark Paluch
6161
* @author Mao Shuai
6262
* @author John Blum
63+
* @author Anne Lee
6364
* @see org.springframework.data.redis.serializer.JacksonObjectReader
6465
* @see org.springframework.data.redis.serializer.JacksonObjectWriter
6566
* @see com.fasterxml.jackson.databind.ObjectMapper
@@ -92,13 +93,13 @@ public GenericJackson2JsonRedisSerializer() {
9293
* In case {@link String name} is {@literal empty} or {@literal null}, then {@link JsonTypeInfo.Id#CLASS} will be
9394
* used.
9495
*
95-
* @param classPropertyTypeName {@link String name} of the JSON property holding type information; can be
96+
* @param typeHintPropertyName {@link String name} of the JSON property holding type information; can be
9697
* {@literal null}.
9798
* @see ObjectMapper#activateDefaultTypingAsProperty(PolymorphicTypeValidator, DefaultTyping, String)
9899
* @see ObjectMapper#activateDefaultTyping(PolymorphicTypeValidator, DefaultTyping, As)
99100
*/
100-
public GenericJackson2JsonRedisSerializer(@Nullable String classPropertyTypeName) {
101-
this(classPropertyTypeName, JacksonObjectReader.create(), JacksonObjectWriter.create());
101+
public GenericJackson2JsonRedisSerializer(@Nullable String typeHintPropertyName) {
102+
this(typeHintPropertyName, JacksonObjectReader.create(), JacksonObjectWriter.create());
102103
}
103104

104105
/**
@@ -109,40 +110,22 @@ public GenericJackson2JsonRedisSerializer(@Nullable String classPropertyTypeName
109110
* In case {@link String name} is {@literal empty} or {@literal null}, then {@link JsonTypeInfo.Id#CLASS} will be
110111
* used.
111112
*
112-
* @param classPropertyTypeName {@link String name} of the JSON property holding type information; can be
113+
* @param typeHintPropertyName {@link String name} of the JSON property holding type information; can be
113114
* {@literal null}.
114115
* @param reader {@link JacksonObjectReader} function to read objects using {@link ObjectMapper}.
115116
* @param writer {@link JacksonObjectWriter} function to write objects using {@link ObjectMapper}.
116117
* @see ObjectMapper#activateDefaultTypingAsProperty(PolymorphicTypeValidator, DefaultTyping, String)
117118
* @see ObjectMapper#activateDefaultTyping(PolymorphicTypeValidator, DefaultTyping, As)
118119
* @since 3.0
119120
*/
120-
public GenericJackson2JsonRedisSerializer(@Nullable String classPropertyTypeName, JacksonObjectReader reader,
121+
public GenericJackson2JsonRedisSerializer(@Nullable String typeHintPropertyName, JacksonObjectReader reader,
121122
JacksonObjectWriter writer) {
122123

123-
this(new ObjectMapper(), reader, writer, classPropertyTypeName);
124+
this(new ObjectMapper(), reader, writer, typeHintPropertyName);
124125

125-
registerNullValueSerializer(this.mapper, classPropertyTypeName);
126+
registerNullValueSerializer(this.mapper, typeHintPropertyName);
126127

127-
StdTypeResolverBuilder typer = TypeResolverBuilder.forEverything(this.mapper).init(JsonTypeInfo.Id.CLASS, null)
128-
.inclusion(JsonTypeInfo.As.PROPERTY);
129-
130-
if (StringUtils.hasText(classPropertyTypeName)) {
131-
typer = typer.typeProperty(classPropertyTypeName);
132-
}
133-
134-
this.mapper.setDefaultTyping(typer);
135-
}
136-
137-
/**
138-
* Factory method returning a {@literal Builder} used to construct and configure a {@link GenericJackson2JsonRedisSerializer}.
139-
*
140-
* @return new {@link GenericJackson2JsonRedisSerializer.GenericJackson2JsonRedisSerializerBuilder}.
141-
* @since 3.3
142-
*/
143-
public static GenericJackson2JsonRedisSerializerBuilder builder(ObjectMapper objectMapper, JacksonObjectReader reader,
144-
JacksonObjectWriter writer) {
145-
return new GenericJackson2JsonRedisSerializerBuilder(objectMapper, reader, writer);
128+
this.mapper.setDefaultTyping(createDefaultTypeResolverBuilder(getObjectMapper(), typeHintPropertyName));
146129
}
147130

148131
/**
@@ -188,7 +171,7 @@ private GenericJackson2JsonRedisSerializer(ObjectMapper mapper, JacksonObjectRea
188171
this.typeResolver = newTypeResolver(mapper, typeHintPropertyName, this.defaultTypingEnabled);
189172
}
190173

191-
private TypeResolver newTypeResolver(ObjectMapper mapper, @Nullable String typeHintPropertyName,
174+
private static TypeResolver newTypeResolver(ObjectMapper mapper, @Nullable String typeHintPropertyName,
192175
Lazy<Boolean> defaultTypingEnabled) {
193176

194177
Lazy<TypeFactory> lazyTypeFactory = Lazy.of(mapper::getTypeFactory);
@@ -199,19 +182,17 @@ private TypeResolver newTypeResolver(ObjectMapper mapper, @Nullable String typeH
199182
return new TypeResolver(lazyTypeFactory, lazyTypeHintPropertyName);
200183
}
201184

202-
private Lazy<String> newLazyTypeHintPropertyName(ObjectMapper mapper, Lazy<Boolean> defaultTypingEnabled) {
185+
private static Lazy<String> newLazyTypeHintPropertyName(ObjectMapper mapper, Lazy<Boolean> defaultTypingEnabled) {
203186

204187
Lazy<String> configuredTypeDeserializationPropertyName = getConfiguredTypeDeserializationPropertyName(mapper);
205188

206-
Lazy<String> resolvedLazyTypeHintPropertyName = Lazy.of(() -> defaultTypingEnabled.get() ? null
207-
: configuredTypeDeserializationPropertyName.get());
208-
209-
resolvedLazyTypeHintPropertyName = resolvedLazyTypeHintPropertyName.or("@class");
189+
Lazy<String> resolvedLazyTypeHintPropertyName = Lazy
190+
.of(() -> defaultTypingEnabled.get() ? null : configuredTypeDeserializationPropertyName.get());
210191

211-
return resolvedLazyTypeHintPropertyName;
192+
return resolvedLazyTypeHintPropertyName.or("@class");
212193
}
213194

214-
private Lazy<String> getConfiguredTypeDeserializationPropertyName(ObjectMapper mapper) {
195+
private static Lazy<String> getConfiguredTypeDeserializationPropertyName(ObjectMapper mapper) {
215196

216197
return Lazy.of(() -> {
217198

@@ -226,20 +207,43 @@ private Lazy<String> getConfiguredTypeDeserializationPropertyName(ObjectMapper m
226207
});
227208
}
228209

210+
private static StdTypeResolverBuilder createDefaultTypeResolverBuilder(ObjectMapper objectMapper,
211+
@Nullable String typeHintPropertyName) {
212+
213+
StdTypeResolverBuilder typer = TypeResolverBuilder.forEverything(objectMapper).init(JsonTypeInfo.Id.CLASS, null)
214+
.inclusion(As.PROPERTY);
215+
216+
if (StringUtils.hasText(typeHintPropertyName)) {
217+
typer = typer.typeProperty(typeHintPropertyName);
218+
}
219+
return typer;
220+
}
221+
222+
/**
223+
* Factory method returning a {@literal Builder} used to construct and configure a
224+
* {@link GenericJackson2JsonRedisSerializer}.
225+
*
226+
* @return new {@link GenericJackson2JsonRedisSerializer.GenericJackson2JsonRedisSerializerBuilder}.
227+
* @since 3.3.1
228+
*/
229+
public static GenericJackson2JsonRedisSerializerBuilder builder() {
230+
return new GenericJackson2JsonRedisSerializerBuilder();
231+
}
232+
229233
/**
230234
* Register {@link NullValueSerializer} in the given {@link ObjectMapper} with an optional
231-
* {@code classPropertyTypeName}. This method should be called by code that customizes
235+
* {@code typeHintPropertyName}. This method should be called by code that customizes
232236
* {@link GenericJackson2JsonRedisSerializer} by providing an external {@link ObjectMapper}.
233237
*
234238
* @param objectMapper the object mapper to customize.
235-
* @param classPropertyTypeName name of the type property. Defaults to {@code @class} if {@literal null}/empty.
239+
* @param typeHintPropertyName name of the type property. Defaults to {@code @class} if {@literal null}/empty.
236240
* @since 2.2
237241
*/
238-
public static void registerNullValueSerializer(ObjectMapper objectMapper, @Nullable String classPropertyTypeName) {
242+
public static void registerNullValueSerializer(ObjectMapper objectMapper, @Nullable String typeHintPropertyName) {
239243

240244
// Simply setting {@code mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)} does not help here
241245
// since we need the type hint embedded for deserialization using the default typing feature.
242-
objectMapper.registerModule(new SimpleModule().addSerializer(new NullValueSerializer(classPropertyTypeName)));
246+
objectMapper.registerModule(new SimpleModule().addSerializer(new NullValueSerializer(typeHintPropertyName)));
243247
}
244248

245249
/**
@@ -376,8 +380,7 @@ protected JavaType resolveType(byte[] source, Class<?> type) throws IOException
376380
*/
377381
private static class NullValueSerializer extends StdSerializer<NullValue> {
378382

379-
@Serial
380-
private static final long serialVersionUID = 1999052150548658808L;
383+
@Serial private static final long serialVersionUID = 1999052150548658808L;
381384

382385
private final String classIdentifier;
383386

@@ -408,65 +411,155 @@ public void serializeWithType(NullValue value, JsonGenerator jsonGenerator, Seri
408411
}
409412

410413
/**
411-
* {@literal Builder} for creating a {@link GenericJackson2JsonRedisSerializer}.
414+
* Builder for configuring and creating a {@link GenericJackson2JsonRedisSerializer}.
412415
*
413416
* @author Anne Lee
414-
* @since 3.3
417+
* @author Mark Paluch
418+
* @since 3.3.1
415419
*/
416420
public static class GenericJackson2JsonRedisSerializerBuilder {
417-
@Nullable
418-
private String classPropertyTypeName;
419-
private JacksonObjectReader reader;
420-
private JacksonObjectWriter writer;
421-
private ObjectMapper mapper;
422-
@Nullable
423-
private StdSerializer<NullValue> nullValueSerializer;
424-
425-
private GenericJackson2JsonRedisSerializerBuilder(
426-
ObjectMapper objectMapper,
427-
JacksonObjectReader reader,
428-
JacksonObjectWriter writer
429-
) {
430-
this.mapper = objectMapper;
421+
422+
private @Nullable String typeHintPropertyName;
423+
424+
private JacksonObjectReader reader = JacksonObjectReader.create();
425+
426+
private JacksonObjectWriter writer = JacksonObjectWriter.create();
427+
428+
private @Nullable ObjectMapper objectMapper;
429+
430+
private @Nullable Boolean defaultTyping;
431+
432+
private boolean registerNullValueSerializer = true;
433+
434+
private @Nullable StdSerializer<NullValue> nullValueSerializer;
435+
436+
private GenericJackson2JsonRedisSerializerBuilder() {}
437+
438+
/**
439+
* Enable or disable default typing. Enabling default typing will override
440+
* {@link ObjectMapper#setDefaultTyping(com.fasterxml.jackson.databind.jsontype.TypeResolverBuilder)} for a given
441+
* {@link ObjectMapper}. Default typing is enabled by default if no {@link ObjectMapper} is provided.
442+
*
443+
* @param defaultTyping whether to enable/disable default typing. Enabled by default if the {@link ObjectMapper} is
444+
* not provided.
445+
* @return this {@link GenericJackson2JsonRedisSerializer.GenericJackson2JsonRedisSerializerBuilder}.
446+
*/
447+
public GenericJackson2JsonRedisSerializerBuilder defaultTyping(boolean defaultTyping) {
448+
this.defaultTyping = defaultTyping;
449+
return this;
450+
}
451+
452+
/**
453+
* Configure a property name to that represents the type hint.
454+
*
455+
* @param typeHintPropertyName {@link String name} of the JSON property holding type information.
456+
* @return this {@link GenericJackson2JsonRedisSerializer.GenericJackson2JsonRedisSerializerBuilder}.
457+
*/
458+
public GenericJackson2JsonRedisSerializerBuilder typeHintPropertyName(String typeHintPropertyName) {
459+
460+
Assert.hasText(typeHintPropertyName, "Type hint property name must bot be null or empty");
461+
462+
this.typeHintPropertyName = typeHintPropertyName;
463+
return this;
464+
}
465+
466+
/**
467+
* Configure a provided {@link ObjectMapper}. Note that the provided {@link ObjectMapper} can be reconfigured with a
468+
* {@link #nullValueSerializer} or default typing depending on the builder configuration.
469+
*
470+
* @param objectMapper must not be {@literal null}.
471+
* @return this {@link GenericJackson2JsonRedisSerializer.GenericJackson2JsonRedisSerializerBuilder}.
472+
*/
473+
public GenericJackson2JsonRedisSerializerBuilder objectMapper(ObjectMapper objectMapper) {
474+
475+
Assert.notNull(objectMapper, "ObjectMapper must not be null");
476+
477+
this.objectMapper = objectMapper;
478+
return this;
479+
}
480+
481+
/**
482+
* Configure {@link JacksonObjectReader}.
483+
*
484+
* @param reader must not be {@literal null}.
485+
* @return this {@link GenericJackson2JsonRedisSerializer.GenericJackson2JsonRedisSerializerBuilder}.
486+
*/
487+
public GenericJackson2JsonRedisSerializerBuilder reader(JacksonObjectReader reader) {
488+
489+
Assert.notNull(reader, "JacksonObjectReader must not be null");
490+
431491
this.reader = reader;
432-
this.writer = writer;
492+
return this;
433493
}
434494

435495
/**
436-
* Configure a classPropertyName.
496+
* Configure {@link JacksonObjectWriter}.
437497
*
438-
* @param classPropertyTypeName can be {@literal null}.
498+
* @param writer must not be {@literal null}.
439499
* @return this {@link GenericJackson2JsonRedisSerializer.GenericJackson2JsonRedisSerializerBuilder}.
440-
* @since 3.3
441500
*/
442-
public GenericJackson2JsonRedisSerializerBuilder classPropertyTypeName(@Nullable String classPropertyTypeName) {
443-
this.classPropertyTypeName = classPropertyTypeName;
501+
public GenericJackson2JsonRedisSerializerBuilder writer(JacksonObjectWriter writer) {
502+
503+
Assert.notNull(writer, "JacksonObjectWriter must not be null");
504+
505+
this.writer = writer;
444506
return this;
445507
}
446508

447509
/**
448-
* Register a nullValueSerializer.
510+
* Register a {@link StdSerializer serializer} for {@link NullValue}.
449511
*
450-
* @param nullValueSerializer the {@link StdSerializer} to use for {@link NullValue} serialization. Can be {@literal null}.
512+
* @param nullValueSerializer the {@link StdSerializer} to use for {@link NullValue} serialization, must not be
513+
* {@literal null}.
451514
* @return this {@link GenericJackson2JsonRedisSerializer.GenericJackson2JsonRedisSerializerBuilder}.
452515
*/
453-
public GenericJackson2JsonRedisSerializerBuilder registerNullValueSerializer(@Nullable StdSerializer<NullValue> nullValueSerializer) {
516+
public GenericJackson2JsonRedisSerializerBuilder nullValueSerializer(StdSerializer<NullValue> nullValueSerializer) {
517+
518+
Assert.notNull(nullValueSerializer, "Null value serializer must not be null");
519+
454520
this.nullValueSerializer = nullValueSerializer;
455521
return this;
456522
}
457523

458524
/**
459-
* Create new instance of {@link GenericJackson2JsonRedisSerializer} with configuration options applied.
525+
* Configure whether to register a {@link StdSerializer serializer} for {@link NullValue} serialization. The default
526+
* serializer considers {@link #typeHintPropertyName(String)}.
460527
*
461-
* @return new instance of {@link GenericJackson2JsonRedisSerializer}.
528+
* @param registerNullValueSerializer {@code true} to register the default serializer; {@code false} otherwise.
529+
* @return this {@link GenericJackson2JsonRedisSerializer.GenericJackson2JsonRedisSerializerBuilder}.
530+
*/
531+
public GenericJackson2JsonRedisSerializerBuilder registerNullValueSerializer(boolean registerNullValueSerializer) {
532+
this.registerNullValueSerializer = registerNullValueSerializer;
533+
return this;
534+
}
535+
536+
/**
537+
* Creates a new instance of {@link GenericJackson2JsonRedisSerializer} with configuration options applied. Creates
538+
* also a new {@link ObjectMapper} if none was provided.
539+
*
540+
* @return a new instance of {@link GenericJackson2JsonRedisSerializer}.
462541
*/
463542
public GenericJackson2JsonRedisSerializer build() {
464-
Assert.notNull(this.mapper, "ObjectMapper must not be null");
465-
Assert.notNull(this.reader, "Reader must not be null");
466-
Assert.notNull(this.writer, "Writer must not be null");
467543

468-
this.mapper.registerModule(new SimpleModule().addSerializer(this.nullValueSerializer != null ? this.nullValueSerializer : new NullValueSerializer(this.classPropertyTypeName)));
469-
return new GenericJackson2JsonRedisSerializer(this.mapper, this.reader, this.writer, this.classPropertyTypeName);
544+
ObjectMapper objectMapper = this.objectMapper;
545+
boolean providedObjectMapper = objectMapper != null;
546+
547+
if (objectMapper == null) {
548+
objectMapper = new ObjectMapper();
549+
}
550+
551+
if (registerNullValueSerializer) {
552+
objectMapper.registerModule(new SimpleModule("GenericJackson2JsonRedisSerializerBuilder")
553+
.addSerializer(this.nullValueSerializer != null ? this.nullValueSerializer
554+
: new NullValueSerializer(this.typeHintPropertyName)));
555+
}
556+
557+
if ((!providedObjectMapper && (defaultTyping == null || defaultTyping))
558+
|| (defaultTyping != null && defaultTyping)) {
559+
objectMapper.setDefaultTyping(createDefaultTypeResolverBuilder(objectMapper, typeHintPropertyName));
560+
}
561+
562+
return new GenericJackson2JsonRedisSerializer(objectMapper, this.reader, this.writer, this.typeHintPropertyName);
470563
}
471564
}
472565

0 commit comments

Comments
 (0)