Support deserialization in GenericJackson2JsonRedisSerializer when using custom JsonFactory.
Closes: #2981 Original Pull Request: #2999
This commit is contained in:
committed by
Christoph Strobl
parent
f2752d1b02
commit
8c74d78872
7
pom.xml
7
pom.xml
@@ -276,6 +276,13 @@
|
|||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.msgpack</groupId>
|
||||||
|
<artifactId>jackson-dataformat-msgpack</artifactId>
|
||||||
|
<version>0.9.8</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>edu.umd.cs.mtc</groupId>
|
<groupId>edu.umd.cs.mtc</groupId>
|
||||||
<artifactId>multithreadedtc</artifactId>
|
<artifactId>multithreadedtc</artifactId>
|
||||||
|
|||||||
@@ -32,13 +32,19 @@ import org.springframework.util.StringUtils;
|
|||||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||||
import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
|
import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
|
||||||
import com.fasterxml.jackson.core.JsonGenerator;
|
import com.fasterxml.jackson.core.JsonGenerator;
|
||||||
|
import com.fasterxml.jackson.core.JsonParser;
|
||||||
|
import com.fasterxml.jackson.core.JsonToken;
|
||||||
import com.fasterxml.jackson.core.TreeNode;
|
import com.fasterxml.jackson.core.TreeNode;
|
||||||
import com.fasterxml.jackson.databind.DeserializationConfig;
|
import com.fasterxml.jackson.databind.DeserializationConfig;
|
||||||
import com.fasterxml.jackson.databind.JavaType;
|
import com.fasterxml.jackson.databind.JavaType;
|
||||||
|
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping;
|
import com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping;
|
||||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||||
|
import com.fasterxml.jackson.databind.deser.BeanDeserializerFactory;
|
||||||
|
import com.fasterxml.jackson.databind.deser.DefaultDeserializationContext;
|
||||||
|
import com.fasterxml.jackson.databind.deser.std.JsonNodeDeserializer;
|
||||||
import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator;
|
import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator;
|
||||||
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
|
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
|
||||||
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
|
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
|
||||||
@@ -179,7 +185,7 @@ public class GenericJackson2JsonRedisSerializer implements RedisSerializer<Objec
|
|||||||
Lazy<String> lazyTypeHintPropertyName = typeHintPropertyName != null ? Lazy.of(typeHintPropertyName)
|
Lazy<String> lazyTypeHintPropertyName = typeHintPropertyName != null ? Lazy.of(typeHintPropertyName)
|
||||||
: newLazyTypeHintPropertyName(mapper, defaultTypingEnabled);
|
: newLazyTypeHintPropertyName(mapper, defaultTypingEnabled);
|
||||||
|
|
||||||
return new TypeResolver(lazyTypeFactory, lazyTypeHintPropertyName);
|
return new TypeResolver(mapper, lazyTypeFactory, lazyTypeHintPropertyName);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Lazy<String> newLazyTypeHintPropertyName(ObjectMapper mapper, Lazy<Boolean> defaultTypingEnabled) {
|
private static Lazy<String> newLazyTypeHintPropertyName(ObjectMapper mapper, Lazy<Boolean> defaultTypingEnabled) {
|
||||||
@@ -340,14 +346,13 @@ public class GenericJackson2JsonRedisSerializer implements RedisSerializer<Objec
|
|||||||
*/
|
*/
|
||||||
static class TypeResolver {
|
static class TypeResolver {
|
||||||
|
|
||||||
// need a separate instance to bypass class hint checks
|
private final ObjectMapper mapper;
|
||||||
private final ObjectMapper mapper = new ObjectMapper();
|
|
||||||
|
|
||||||
private final Supplier<TypeFactory> typeFactory;
|
private final Supplier<TypeFactory> typeFactory;
|
||||||
private final Supplier<String> hintName;
|
private final Supplier<String> hintName;
|
||||||
|
|
||||||
TypeResolver(Supplier<TypeFactory> typeFactory, Supplier<String> hintName) {
|
TypeResolver(ObjectMapper mapper, Supplier<TypeFactory> typeFactory, Supplier<String> hintName) {
|
||||||
|
|
||||||
|
this.mapper = mapper;
|
||||||
this.typeFactory = typeFactory;
|
this.typeFactory = typeFactory;
|
||||||
this.hintName = hintName;
|
this.hintName = hintName;
|
||||||
}
|
}
|
||||||
@@ -358,7 +363,7 @@ public class GenericJackson2JsonRedisSerializer implements RedisSerializer<Objec
|
|||||||
|
|
||||||
protected JavaType resolveType(byte[] source, Class<?> type) throws IOException {
|
protected JavaType resolveType(byte[] source, Class<?> type) throws IOException {
|
||||||
|
|
||||||
JsonNode root = mapper.readTree(source);
|
JsonNode root = readTree(source);
|
||||||
JsonNode jsonNode = root.get(hintName.get());
|
JsonNode jsonNode = root.get(hintName.get());
|
||||||
|
|
||||||
if (jsonNode instanceof TextNode && jsonNode.asText() != null) {
|
if (jsonNode instanceof TextNode && jsonNode.asText() != null) {
|
||||||
@@ -367,6 +372,42 @@ public class GenericJackson2JsonRedisSerializer implements RedisSerializer<Objec
|
|||||||
|
|
||||||
return constructType(type);
|
return constructType(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lenient variant of ObjectMapper._readTreeAndClose using a strict {@link JsonNodeDeserializer}.
|
||||||
|
*
|
||||||
|
* @param source
|
||||||
|
* @return
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
private JsonNode readTree(byte[] source) throws IOException {
|
||||||
|
|
||||||
|
JsonDeserializer<? extends JsonNode> deserializer = JsonNodeDeserializer.getDeserializer(JsonNode.class);
|
||||||
|
DeserializationConfig cfg = mapper.getDeserializationConfig();
|
||||||
|
|
||||||
|
try (JsonParser parser = mapper.createParser(source)) {
|
||||||
|
|
||||||
|
cfg.initialize(parser);
|
||||||
|
JsonToken t = parser.currentToken();
|
||||||
|
if (t == null) {
|
||||||
|
t = parser.nextToken();
|
||||||
|
if (t == null) {
|
||||||
|
return cfg.getNodeFactory().missingNode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Hokey pokey! Oh my.
|
||||||
|
*/
|
||||||
|
DefaultDeserializationContext ctxt = new DefaultDeserializationContext.Impl(BeanDeserializerFactory.instance)
|
||||||
|
.createInstance(cfg, parser, mapper.getInjectableValues());
|
||||||
|
if (t == JsonToken.VALUE_NULL) {
|
||||||
|
return cfg.getNodeFactory().nullNode();
|
||||||
|
} else {
|
||||||
|
return deserializer.deserialize(parser, ctxt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import java.util.function.Consumer;
|
|||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.mockito.Mockito;
|
import org.mockito.Mockito;
|
||||||
|
import org.msgpack.jackson.dataformat.MessagePackFactory;
|
||||||
|
|
||||||
import org.springframework.beans.BeanUtils;
|
import org.springframework.beans.BeanUtils;
|
||||||
import org.springframework.cache.support.NullValue;
|
import org.springframework.cache.support.NullValue;
|
||||||
@@ -449,6 +450,7 @@ class GenericJackson2JsonRedisSerializerUnitTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void defaultSerializeAndDeserializeNullValueWithBuilderClass() {
|
void defaultSerializeAndDeserializeNullValueWithBuilderClass() {
|
||||||
|
|
||||||
GenericJackson2JsonRedisSerializer serializer = GenericJackson2JsonRedisSerializer.builder()
|
GenericJackson2JsonRedisSerializer serializer = GenericJackson2JsonRedisSerializer.builder()
|
||||||
.objectMapper(new ObjectMapper().enableDefaultTyping(DefaultTyping.EVERYTHING, As.PROPERTY))
|
.objectMapper(new ObjectMapper().enableDefaultTyping(DefaultTyping.EVERYTHING, As.PROPERTY))
|
||||||
.build();
|
.build();
|
||||||
@@ -487,6 +489,31 @@ class GenericJackson2JsonRedisSerializerUnitTests {
|
|||||||
assertThat(deserializedValue).isNull();
|
assertThat(deserializedValue).isNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test // GH-2981
|
||||||
|
void defaultSerializeAndDeserializeWithCustomJsonFactory() {
|
||||||
|
|
||||||
|
GenericJackson2JsonRedisSerializer serializer = GenericJackson2JsonRedisSerializer.builder()
|
||||||
|
.objectMapper(
|
||||||
|
new ObjectMapper(new MessagePackFactory()).enableDefaultTyping(DefaultTyping.EVERYTHING, As.PROPERTY))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
byte[] serializedValue = serializer.serialize(COMPLEX_OBJECT);
|
||||||
|
|
||||||
|
Object deserializedValue = serializer.deserialize(serializedValue, Object.class);
|
||||||
|
assertThat(deserializedValue).isEqualTo(COMPLEX_OBJECT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test // GH-2981
|
||||||
|
void defaultSerializeAndDeserializeNullValueWithBuilderClassAndCustomJsonFactory() {
|
||||||
|
|
||||||
|
GenericJackson2JsonRedisSerializer serializer = GenericJackson2JsonRedisSerializer.builder()
|
||||||
|
.objectMapper(
|
||||||
|
new ObjectMapper(new MessagePackFactory()).enableDefaultTyping(DefaultTyping.EVERYTHING, As.PROPERTY))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
serializeAndDeserializeNullValue(serializer);
|
||||||
|
}
|
||||||
|
|
||||||
private static void serializeAndDeserializeNullValue(GenericJackson2JsonRedisSerializer serializer) {
|
private static void serializeAndDeserializeNullValue(GenericJackson2JsonRedisSerializer serializer) {
|
||||||
|
|
||||||
NullValue nv = BeanUtils.instantiateClass(NullValue.class);
|
NullValue nv = BeanUtils.instantiateClass(NullValue.class);
|
||||||
|
|||||||
Reference in New Issue
Block a user