Introduce custom StdTypeResolverBuilder to support primitive arrays without type hints.

Closes: #2361
Original Pull Request: #2364
This commit is contained in:
Mark Paluch
2022-07-13 16:05:02 +02:00
committed by Christoph Strobl
parent 0c51d99e9b
commit 0e23a805ca
2 changed files with 85 additions and 6 deletions

View File

@@ -20,17 +20,21 @@ import java.io.IOException;
import org.springframework.cache.support.NullValue;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.TreeNode;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
import com.fasterxml.jackson.databind.jsontype.impl.StdTypeResolverBuilder;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.SerializerFactory;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
@@ -71,12 +75,15 @@ public class GenericJackson2JsonRedisSerializer implements RedisSerializer<Objec
// the type hint embedded for deserialization using the default typing feature.
registerNullValueSerializer(mapper, classPropertyTypeName);
StdTypeResolverBuilder typer = new TypeResolverBuilder(DefaultTyping.EVERYTHING,
mapper.getPolymorphicTypeValidator());
typer = typer.init(JsonTypeInfo.Id.CLASS, null);
typer = typer.inclusion(JsonTypeInfo.As.PROPERTY);
if (StringUtils.hasText(classPropertyTypeName)) {
mapper.activateDefaultTypingAsProperty(mapper.getPolymorphicTypeValidator(), DefaultTyping.EVERYTHING,
classPropertyTypeName);
} else {
mapper.activateDefaultTyping(mapper.getPolymorphicTypeValidator(), DefaultTyping.EVERYTHING, As.PROPERTY);
typer = typer.typeProperty(classPropertyTypeName);
}
mapper.setDefaultTyping(typer);
}
/**
@@ -184,8 +191,7 @@ public class GenericJackson2JsonRedisSerializer implements RedisSerializer<Objec
* @see com.fasterxml.jackson.databind.ser.std.StdSerializer#serialize(java.lang.Object, com.fasterxml.jackson.core.JsonGenerator, com.fasterxml.jackson.databind.SerializerProvider)
*/
@Override
public void serialize(NullValue value, JsonGenerator jgen, SerializerProvider provider)
throws IOException {
public void serialize(NullValue value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
jgen.writeStartObject();
jgen.writeStringField(classIdentifier, NullValue.class.getName());
@@ -198,4 +204,52 @@ public class GenericJackson2JsonRedisSerializer implements RedisSerializer<Objec
serialize(value, gen, serializers);
}
}
/**
* 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
* those requirements.
*
* @author Mark Paluch
* @since 2.7.2
*/
private static class TypeResolverBuilder extends ObjectMapper.DefaultTypeResolverBuilder {
public TypeResolverBuilder(DefaultTyping t, PolymorphicTypeValidator ptv) {
super(t, ptv);
}
@Override
public ObjectMapper.DefaultTypeResolverBuilder withDefaultImpl(Class<?> defaultImpl) {
return this;
}
/**
* Method called to check if the default type handler should be used for given type. Note: "natural types" (String,
* Boolean, Integer, Double) will never use typing; that is both due to them being concrete and final, and since
* actual serializers and deserializers will also ignore any attempts to enforce typing.
*/
public boolean useForType(JavaType t) {
if (t.isJavaLangObject()) {
return true;
}
while (t.isArrayType()) {
t = t.getContentType();
}
if (ClassUtils.isPrimitiveOrWrapper(t.getRawClass())) {
return false;
}
// 19-Apr-2016, tatu: ReferenceType like Optional also requires similar handling:
while (t.isReferenceType()) {
t = t.getReferencedType();
}
// [databind#88] Should not apply to JSON tree models:
return !TreeNode.class.isAssignableFrom(t.getRawClass());
}
}
}

View File

@@ -157,9 +157,26 @@ class GenericJackson2JsonRedisSerializerUnitTests {
FinalObject source = new FinalObject();
source.longValue = 1L;
source.myArray = new int[] { 1, 2, 3 };
source.simpleObject = new SimpleObject(2L);
assertThat(serializer.deserialize(serializer.serialize(source))).isEqualTo(source);
assertThat(serializer.deserialize(
("{\"@class\":\"org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializerUnitTests$FinalObject\",\"longValue\":1,\"myArray\":[1,2,3],\n"
+ "\"simpleObject\":{\"@class\":\"org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializerUnitTests$SimpleObject\",\"longValue\":2}}")
.getBytes())).isEqualTo(source);
}
@Test // GH-2361
void shouldDeserializeArrayWithoutTypeHint() {
GenericJackson2JsonRedisSerializer gs = new GenericJackson2JsonRedisSerializer();
CountAndArray result = (CountAndArray) gs.deserialize(
("{\"@class\":\"org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializerUnitTests$CountAndArray\", \"count\":1, \"available\":[0,1]}")
.getBytes());
assertThat(result.getCount()).isEqualTo(1);
assertThat(result.getAvailable()).containsExactly(0, 1);
}
private static void serializeAndDeserializeNullValue(GenericJackson2JsonRedisSerializer serializer) {
@@ -217,6 +234,7 @@ class GenericJackson2JsonRedisSerializerUnitTests {
@Data
static final class FinalObject {
public Long longValue;
public int[] myArray;
SimpleObject simpleObject;
}
@@ -252,4 +270,11 @@ class GenericJackson2JsonRedisSerializerUnitTests {
}
}
@Data
static class CountAndArray {
private int count;
private int[] available;
}
}