Skip to content

Commit 6b56cfb

Browse files
AnneMayormp911de
authored andcommitted
Allow configuring custom NullValueSerializer.
Closes #2878 Original pull request: #2905
1 parent c89c597 commit 6b56cfb

File tree

2 files changed

+124
-3
lines changed

2 files changed

+124
-3
lines changed

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

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,17 @@ public GenericJackson2JsonRedisSerializer(@Nullable String classPropertyTypeName
134134
this.mapper.setDefaultTyping(typer);
135135
}
136136

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);
146+
}
147+
137148
/**
138149
* Setting a custom-configured {@link ObjectMapper} is one way to take further control of the JSON serialization
139150
* process. For example, an extended {@link SerializerFactory} can be configured that provides custom serializers for
@@ -396,6 +407,69 @@ public void serializeWithType(NullValue value, JsonGenerator jsonGenerator, Seri
396407
}
397408
}
398409

410+
/**
411+
* {@literal Builder} for creating a {@link GenericJackson2JsonRedisSerializer}.
412+
*
413+
* @author Anne Lee
414+
* @since 3.3
415+
*/
416+
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;
431+
this.reader = reader;
432+
this.writer = writer;
433+
}
434+
435+
/**
436+
* Configure a classPropertyName.
437+
*
438+
* @param classPropertyTypeName can be {@literal null}.
439+
* @return this {@link GenericJackson2JsonRedisSerializer.GenericJackson2JsonRedisSerializerBuilder}.
440+
* @since 3.3
441+
*/
442+
public GenericJackson2JsonRedisSerializerBuilder classPropertyTypeName(@Nullable String classPropertyTypeName) {
443+
this.classPropertyTypeName = classPropertyTypeName;
444+
return this;
445+
}
446+
447+
/**
448+
* Register a nullValueSerializer.
449+
*
450+
* @param nullValueSerializer the {@link StdSerializer} to use for {@link NullValue} serialization. Can be {@literal null}.
451+
* @return this {@link GenericJackson2JsonRedisSerializer.GenericJackson2JsonRedisSerializerBuilder}.
452+
*/
453+
public GenericJackson2JsonRedisSerializerBuilder registerNullValueSerializer(@Nullable StdSerializer<NullValue> nullValueSerializer) {
454+
this.nullValueSerializer = nullValueSerializer;
455+
return this;
456+
}
457+
458+
/**
459+
* Create new instance of {@link GenericJackson2JsonRedisSerializer} with configuration options applied.
460+
*
461+
* @return new instance of {@link GenericJackson2JsonRedisSerializer}.
462+
*/
463+
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");
467+
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);
470+
}
471+
}
472+
399473
/**
400474
* Custom {@link StdTypeResolverBuilder} that considers typing for non-primitive types. Primitives, their wrappers and
401475
* primitive arrays do not require type hints. The default {@code DefaultTyping#EVERYTHING} typing does not satisfy

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

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@
4040
import java.util.concurrent.atomic.AtomicReference;
4141
import java.util.function.Consumer;
4242

43+
import com.fasterxml.jackson.core.JsonGenerator;
44+
import com.fasterxml.jackson.databind.SerializerProvider;
45+
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
46+
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
4347
import org.junit.jupiter.api.Test;
4448
import org.mockito.Mockito;
4549

@@ -420,7 +424,7 @@ void deserializesJavaTimeFrimBytes() {
420424
}
421425

422426
@Test // GH-2601
423-
public void internalObjectMapperCustomization() {
427+
void internalObjectMapperCustomization() {
424428

425429
GenericJackson2JsonRedisSerializer serializer = new GenericJackson2JsonRedisSerializer();
426430

@@ -432,20 +436,63 @@ public void internalObjectMapperCustomization() {
432436

433437
assertThat(serializer.configure(configurer)).isSameAs(serializer);
434438

435-
verify(mockObjectMapper, times(1)).registerModule(eq(mockModule));
439+
verify(mockObjectMapper, times(1)).registerModule(mockModule);
436440
verifyNoMoreInteractions(mockObjectMapper);
437441
verifyNoInteractions(mockModule);
438442
}
439443

440444
@Test // GH-2601
441-
public void configureWithNullConsumerThrowsIllegalArgumentException() {
445+
void configureWithNullConsumerThrowsIllegalArgumentException() {
442446

443447
assertThatIllegalArgumentException()
444448
.isThrownBy(() -> new GenericJackson2JsonRedisSerializer().configure(null))
445449
.withMessage("Consumer used to configure and customize ObjectMapper must not be null")
446450
.withNoCause();
447451
}
448452

453+
@Test
454+
void defaultSerializeAndDeserializeNullValueWithBuilderClass() {
455+
GenericJackson2JsonRedisSerializer serializer = GenericJackson2JsonRedisSerializer.builder(
456+
new ObjectMapper().enableDefaultTyping(DefaultTyping.EVERYTHING, As.PROPERTY),
457+
JacksonObjectReader.create(), JacksonObjectWriter.create())
458+
.classPropertyTypeName(null)
459+
.registerNullValueSerializer(null)
460+
.build();
461+
462+
serializeAndDeserializeNullValue(serializer);
463+
}
464+
465+
@Test
466+
void customSerializeAndDeserializeNullValueWithBuilderClass() {
467+
GenericJackson2JsonRedisSerializer serializer = GenericJackson2JsonRedisSerializer.builder(
468+
new ObjectMapper(), JacksonObjectReader.create(), JacksonObjectWriter.create())
469+
.classPropertyTypeName(null)
470+
.registerNullValueSerializer(new StdSerializer<>(NullValue.class) {
471+
@Override
472+
public void serialize(NullValue nullValue,
473+
JsonGenerator jsonGenerator,
474+
SerializerProvider serializerProvider) throws IOException {
475+
jsonGenerator.writeNull();
476+
}
477+
478+
@Override
479+
public void serializeWithType(NullValue value, JsonGenerator jsonGenerator, SerializerProvider serializers,
480+
TypeSerializer typeSerializer) throws IOException {
481+
482+
serialize(value, jsonGenerator, serializers);
483+
}
484+
})
485+
.build();
486+
487+
NullValue nv = BeanUtils.instantiateClass(NullValue.class);
488+
489+
byte[] serializedValue = serializer.serialize(nv);
490+
assertThat(serializedValue).isNotNull();
491+
492+
Object deserializedValue = serializer.deserialize(serializedValue);
493+
assertThat(deserializedValue).isNull();
494+
}
495+
449496
private static void serializeAndDeserializeNullValue(GenericJackson2JsonRedisSerializer serializer) {
450497

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

0 commit comments

Comments
 (0)