Skip to content

Commit b43c98a

Browse files
mp911dechristophstrobl
authored andcommitted
Introduce custom StdTypeResolverBuilder to support primitive arrays without type hints.
Closes: #2361 Original Pull Request: #2364
1 parent c600d0e commit b43c98a

File tree

2 files changed

+84
-4
lines changed

2 files changed

+84
-4
lines changed

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

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,22 @@
2323
import org.springframework.data.util.Lazy;
2424
import org.springframework.lang.Nullable;
2525
import org.springframework.util.Assert;
26+
import org.springframework.util.ClassUtils;
2627
import org.springframework.util.StringUtils;
2728

2829
import com.fasterxml.jackson.annotation.JsonTypeInfo;
2930
import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
3031
import com.fasterxml.jackson.core.JsonGenerator;
32+
import com.fasterxml.jackson.core.JsonProcessingException;
33+
import com.fasterxml.jackson.core.TreeNode;
3134
import com.fasterxml.jackson.databind.JavaType;
3235
import com.fasterxml.jackson.databind.JsonNode;
3336
import com.fasterxml.jackson.databind.ObjectMapper;
3437
import com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping;
3538
import com.fasterxml.jackson.databind.SerializerProvider;
3639
import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator;
3740
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
41+
import com.fasterxml.jackson.databind.jsontype.impl.StdTypeResolverBuilder;
3842
import com.fasterxml.jackson.databind.module.SimpleModule;
3943
import com.fasterxml.jackson.databind.node.TextNode;
4044
import com.fasterxml.jackson.databind.ser.SerializerFactory;
@@ -105,12 +109,15 @@ public GenericJackson2JsonRedisSerializer(@Nullable String classPropertyTypeName
105109
// the type hint embedded for deserialization using the default typing feature.
106110
registerNullValueSerializer(mapper, classPropertyTypeName);
107111

112+
StdTypeResolverBuilder typer = new TypeResolverBuilder(DefaultTyping.EVERYTHING,
113+
mapper.getPolymorphicTypeValidator());
114+
typer = typer.init(JsonTypeInfo.Id.CLASS, null);
115+
typer = typer.inclusion(JsonTypeInfo.As.PROPERTY);
116+
108117
if (StringUtils.hasText(classPropertyTypeName)) {
109-
mapper.activateDefaultTypingAsProperty(mapper.getPolymorphicTypeValidator(), DefaultTyping.EVERYTHING,
110-
classPropertyTypeName);
111-
} else {
112-
mapper.activateDefaultTyping(mapper.getPolymorphicTypeValidator(), DefaultTyping.EVERYTHING, As.PROPERTY);
118+
typer = typer.typeProperty(classPropertyTypeName);
113119
}
120+
mapper.setDefaultTyping(typer);
114121
}
115122

116123
/**
@@ -311,4 +318,52 @@ public void serializeWithType(NullValue value, JsonGenerator gen, SerializerProv
311318
serialize(value, gen, serializers);
312319
}
313320
}
321+
322+
/**
323+
* Custom {@link StdTypeResolverBuilder} that considers typing for non-primitive types. Primitives, their wrappers and
324+
* primitive arrays do not require type hints. The default {@code DefaultTyping#EVERYTHING} typing does not satisfy
325+
* those requirements.
326+
*
327+
* @author Mark Paluch
328+
* @since 2.7.2
329+
*/
330+
private static class TypeResolverBuilder extends ObjectMapper.DefaultTypeResolverBuilder {
331+
332+
public TypeResolverBuilder(DefaultTyping t, PolymorphicTypeValidator ptv) {
333+
super(t, ptv);
334+
}
335+
336+
@Override
337+
public ObjectMapper.DefaultTypeResolverBuilder withDefaultImpl(Class<?> defaultImpl) {
338+
return this;
339+
}
340+
341+
/**
342+
* Method called to check if the default type handler should be used for given type. Note: "natural types" (String,
343+
* Boolean, Integer, Double) will never use typing; that is both due to them being concrete and final, and since
344+
* actual serializers and deserializers will also ignore any attempts to enforce typing.
345+
*/
346+
public boolean useForType(JavaType t) {
347+
348+
if (t.isJavaLangObject()) {
349+
return true;
350+
}
351+
352+
while (t.isArrayType()) {
353+
t = t.getContentType();
354+
}
355+
356+
if (ClassUtils.isPrimitiveOrWrapper(t.getRawClass())) {
357+
return false;
358+
}
359+
360+
// 19-Apr-2016, tatu: ReferenceType like Optional also requires similar handling:
361+
while (t.isReferenceType()) {
362+
t = t.getReferencedType();
363+
}
364+
365+
// [databind#88] Should not apply to JSON tree models:
366+
return !TreeNode.class.isAssignableFrom(t.getRawClass());
367+
}
368+
}
314369
}

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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,9 +160,26 @@ void deserializeShouldBeAbleToRestoreFinalObjectAfterSerialization() {
160160

161161
FinalObject source = new FinalObject();
162162
source.longValue = 1L;
163+
source.myArray = new int[] { 1, 2, 3 };
163164
source.simpleObject = new SimpleObject(2L);
164165

165166
assertThat(serializer.deserialize(serializer.serialize(source))).isEqualTo(source);
167+
assertThat(serializer.deserialize(
168+
("{\"@class\":\"org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializerUnitTests$FinalObject\",\"longValue\":1,\"myArray\":[1,2,3],\n"
169+
+ "\"simpleObject\":{\"@class\":\"org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializerUnitTests$SimpleObject\",\"longValue\":2}}")
170+
.getBytes())).isEqualTo(source);
171+
}
172+
173+
@Test // GH-2361
174+
void shouldDeserializeArrayWithoutTypeHint() {
175+
176+
GenericJackson2JsonRedisSerializer gs = new GenericJackson2JsonRedisSerializer();
177+
CountAndArray result = (CountAndArray) gs.deserialize(
178+
("{\"@class\":\"org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializerUnitTests$CountAndArray\", \"count\":1, \"available\":[0,1]}")
179+
.getBytes());
180+
181+
assertThat(result.getCount()).isEqualTo(1);
182+
assertThat(result.getAvailable()).containsExactly(0, 1);
166183
}
167184

168185
@Test // GH-2322
@@ -300,6 +317,7 @@ public boolean equals(Object obj) {
300317
@Data
301318
static final class FinalObject {
302319
public Long longValue;
320+
public int[] myArray;
303321
SimpleObject simpleObject;
304322
}
305323

@@ -349,4 +367,11 @@ interface Basic {}
349367

350368
interface Detailed {}
351369
}
370+
371+
@Data
372+
static class CountAndArray {
373+
374+
private int count;
375+
private int[] available;
376+
}
352377
}

0 commit comments

Comments
 (0)