Skip to content

Allow configuring custom NullValueSerializer #2905

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,17 @@ public GenericJackson2JsonRedisSerializer(@Nullable String classPropertyTypeName
this.mapper.setDefaultTyping(typer);
}

/**
* Factory method returning a {@literal Builder} used to construct and configure a {@link GenericJackson2JsonRedisSerializer}.
*
* @return new {@link GenericJackson2JsonRedisSerializer.GenericJackson2JsonRedisSerializerBuilder}.
* @since 3.3
*/
public static GenericJackson2JsonRedisSerializerBuilder builder(ObjectMapper objectMapper, JacksonObjectReader reader,
JacksonObjectWriter writer) {
return new GenericJackson2JsonRedisSerializerBuilder(objectMapper, reader, writer);
}

/**
* Setting a custom-configured {@link ObjectMapper} is one way to take further control of the JSON serialization
* process. For example, an extended {@link SerializerFactory} can be configured that provides custom serializers for
Expand Down Expand Up @@ -396,6 +407,69 @@ public void serializeWithType(NullValue value, JsonGenerator jsonGenerator, Seri
}
}

/**
* {@literal Builder} for creating a {@link GenericJackson2JsonRedisSerializer}.
*
* @author Anne Lee
* @since 3.3
*/
public static class GenericJackson2JsonRedisSerializerBuilder {
@Nullable
private String classPropertyTypeName;
private JacksonObjectReader reader;
private JacksonObjectWriter writer;
private ObjectMapper mapper;
@Nullable
private StdSerializer<NullValue> nullValueSerializer;

private GenericJackson2JsonRedisSerializerBuilder(
ObjectMapper objectMapper,
JacksonObjectReader reader,
JacksonObjectWriter writer
) {
this.mapper = objectMapper;
this.reader = reader;
this.writer = writer;
}

/**
* Configure a classPropertyName.
*
* @param classPropertyTypeName can be {@literal null}.
* @return this {@link GenericJackson2JsonRedisSerializer.GenericJackson2JsonRedisSerializerBuilder}.
* @since 3.3
*/
public GenericJackson2JsonRedisSerializerBuilder classPropertyTypeName(@Nullable String classPropertyTypeName) {
this.classPropertyTypeName = classPropertyTypeName;
return this;
}

/**
* Register a nullValueSerializer.
*
* @param nullValueSerializer the {@link StdSerializer} to use for {@link NullValue} serialization. Can be {@literal null}.
* @return this {@link GenericJackson2JsonRedisSerializer.GenericJackson2JsonRedisSerializerBuilder}.
*/
public GenericJackson2JsonRedisSerializerBuilder registerNullValueSerializer(@Nullable StdSerializer<NullValue> nullValueSerializer) {
this.nullValueSerializer = nullValueSerializer;
return this;
}

/**
* Create new instance of {@link GenericJackson2JsonRedisSerializer} with configuration options applied.
*
* @return new instance of {@link GenericJackson2JsonRedisSerializer}.
*/
public GenericJackson2JsonRedisSerializer build() {
Assert.notNull(this.mapper, "ObjectMapper must not be null");
Assert.notNull(this.reader, "Reader must not be null");
Assert.notNull(this.writer, "Writer must not be null");

this.mapper.registerModule(new SimpleModule().addSerializer(this.nullValueSerializer != null ? this.nullValueSerializer : new NullValueSerializer(this.classPropertyTypeName)));
return new GenericJackson2JsonRedisSerializer(this.mapper, this.reader, this.writer, this.classPropertyTypeName);
}
}

/**
* Custom {@link StdTypeResolverBuilder} that considers typing for non-primitive types. Primitives, their wrappers and
* primitive arrays do not require type hints. The default {@code DefaultTyping#EVERYTHING} typing does not satisfy
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;

Expand Down Expand Up @@ -420,7 +424,7 @@ void deserializesJavaTimeFrimBytes() {
}

@Test // GH-2601
public void internalObjectMapperCustomization() {
Copy link
Contributor Author

@AnneMayor AnneMayor May 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just removed according to sonarlint rules. It looks like an unnecessary keyword.

void internalObjectMapperCustomization() {

GenericJackson2JsonRedisSerializer serializer = new GenericJackson2JsonRedisSerializer();

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

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

verify(mockObjectMapper, times(1)).registerModule(eq(mockModule));
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just removed unnecessary command.

verify(mockObjectMapper, times(1)).registerModule(mockModule);
verifyNoMoreInteractions(mockObjectMapper);
verifyNoInteractions(mockModule);
}

@Test // GH-2601
public void configureWithNullConsumerThrowsIllegalArgumentException() {
Copy link
Contributor Author

@AnneMayor AnneMayor May 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just removed according to sonarlint rules. It looks like an unnecessary keyword.

void configureWithNullConsumerThrowsIllegalArgumentException() {

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

@Test
void defaultSerializeAndDeserializeNullValueWithBuilderClass() {
GenericJackson2JsonRedisSerializer serializer = GenericJackson2JsonRedisSerializer.builder(
new ObjectMapper().enableDefaultTyping(DefaultTyping.EVERYTHING, As.PROPERTY),
JacksonObjectReader.create(), JacksonObjectWriter.create())
.classPropertyTypeName(null)
.registerNullValueSerializer(null)
.build();

serializeAndDeserializeNullValue(serializer);
}

@Test
void customSerializeAndDeserializeNullValueWithBuilderClass() {
GenericJackson2JsonRedisSerializer serializer = GenericJackson2JsonRedisSerializer.builder(
new ObjectMapper(), JacksonObjectReader.create(), JacksonObjectWriter.create())
.classPropertyTypeName(null)
.registerNullValueSerializer(new StdSerializer<>(NullValue.class) {
@Override
public void serialize(NullValue nullValue,
JsonGenerator jsonGenerator,
SerializerProvider serializerProvider) throws IOException {
jsonGenerator.writeNull();
}

@Override
public void serializeWithType(NullValue value, JsonGenerator jsonGenerator, SerializerProvider serializers,
TypeSerializer typeSerializer) throws IOException {

serialize(value, jsonGenerator, serializers);
}
})
.build();

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

byte[] serializedValue = serializer.serialize(nv);
assertThat(serializedValue).isNotNull();

Object deserializedValue = serializer.deserialize(serializedValue);
assertThat(deserializedValue).isNull();
}

private static void serializeAndDeserializeNullValue(GenericJackson2JsonRedisSerializer serializer) {

NullValue nv = BeanUtils.instantiateClass(NullValue.class);
Expand Down