Skip to content

Commit 0e23a80

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

File tree

2 files changed

+85
-6
lines changed

2 files changed

+85
-6
lines changed

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

Lines changed: 60 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,21 @@
2020
import org.springframework.cache.support.NullValue;
2121
import org.springframework.lang.Nullable;
2222
import org.springframework.util.Assert;
23+
import org.springframework.util.ClassUtils;
2324
import org.springframework.util.StringUtils;
2425

2526
import com.fasterxml.jackson.annotation.JsonTypeInfo;
2627
import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
2728
import com.fasterxml.jackson.core.JsonGenerator;
2829
import com.fasterxml.jackson.core.JsonProcessingException;
30+
import com.fasterxml.jackson.core.TreeNode;
31+
import com.fasterxml.jackson.databind.JavaType;
2932
import com.fasterxml.jackson.databind.ObjectMapper;
3033
import com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping;
3134
import com.fasterxml.jackson.databind.SerializerProvider;
3235
import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator;
3336
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
37+
import com.fasterxml.jackson.databind.jsontype.impl.StdTypeResolverBuilder;
3438
import com.fasterxml.jackson.databind.module.SimpleModule;
3539
import com.fasterxml.jackson.databind.ser.SerializerFactory;
3640
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
@@ -71,12 +75,15 @@ public GenericJackson2JsonRedisSerializer(@Nullable String classPropertyTypeName
7175
// the type hint embedded for deserialization using the default typing feature.
7276
registerNullValueSerializer(mapper, classPropertyTypeName);
7377

78+
StdTypeResolverBuilder typer = new TypeResolverBuilder(DefaultTyping.EVERYTHING,
79+
mapper.getPolymorphicTypeValidator());
80+
typer = typer.init(JsonTypeInfo.Id.CLASS, null);
81+
typer = typer.inclusion(JsonTypeInfo.As.PROPERTY);
82+
7483
if (StringUtils.hasText(classPropertyTypeName)) {
75-
mapper.activateDefaultTypingAsProperty(mapper.getPolymorphicTypeValidator(), DefaultTyping.EVERYTHING,
76-
classPropertyTypeName);
77-
} else {
78-
mapper.activateDefaultTyping(mapper.getPolymorphicTypeValidator(), DefaultTyping.EVERYTHING, As.PROPERTY);
84+
typer = typer.typeProperty(classPropertyTypeName);
7985
}
86+
mapper.setDefaultTyping(typer);
8087
}
8188

8289
/**
@@ -184,8 +191,7 @@ private static class NullValueSerializer extends StdSerializer<NullValue> {
184191
* @see com.fasterxml.jackson.databind.ser.std.StdSerializer#serialize(java.lang.Object, com.fasterxml.jackson.core.JsonGenerator, com.fasterxml.jackson.databind.SerializerProvider)
185192
*/
186193
@Override
187-
public void serialize(NullValue value, JsonGenerator jgen, SerializerProvider provider)
188-
throws IOException {
194+
public void serialize(NullValue value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
189195

190196
jgen.writeStartObject();
191197
jgen.writeStringField(classIdentifier, NullValue.class.getName());
@@ -198,4 +204,52 @@ public void serializeWithType(NullValue value, JsonGenerator gen, SerializerProv
198204
serialize(value, gen, serializers);
199205
}
200206
}
207+
208+
/**
209+
* Custom {@link StdTypeResolverBuilder} that considers typing for non-primitive types. Primitives, their wrappers and
210+
* primitive arrays do not require type hints. The default {@code DefaultTyping#EVERYTHING} typing does not satisfy
211+
* those requirements.
212+
*
213+
* @author Mark Paluch
214+
* @since 2.7.2
215+
*/
216+
private static class TypeResolverBuilder extends ObjectMapper.DefaultTypeResolverBuilder {
217+
218+
public TypeResolverBuilder(DefaultTyping t, PolymorphicTypeValidator ptv) {
219+
super(t, ptv);
220+
}
221+
222+
@Override
223+
public ObjectMapper.DefaultTypeResolverBuilder withDefaultImpl(Class<?> defaultImpl) {
224+
return this;
225+
}
226+
227+
/**
228+
* Method called to check if the default type handler should be used for given type. Note: "natural types" (String,
229+
* Boolean, Integer, Double) will never use typing; that is both due to them being concrete and final, and since
230+
* actual serializers and deserializers will also ignore any attempts to enforce typing.
231+
*/
232+
public boolean useForType(JavaType t) {
233+
234+
if (t.isJavaLangObject()) {
235+
return true;
236+
}
237+
238+
while (t.isArrayType()) {
239+
t = t.getContentType();
240+
}
241+
242+
if (ClassUtils.isPrimitiveOrWrapper(t.getRawClass())) {
243+
return false;
244+
}
245+
246+
// 19-Apr-2016, tatu: ReferenceType like Optional also requires similar handling:
247+
while (t.isReferenceType()) {
248+
t = t.getReferencedType();
249+
}
250+
251+
// [databind#88] Should not apply to JSON tree models:
252+
return !TreeNode.class.isAssignableFrom(t.getRawClass());
253+
}
254+
}
201255
}

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

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

158158
FinalObject source = new FinalObject();
159159
source.longValue = 1L;
160+
source.myArray = new int[] { 1, 2, 3 };
160161
source.simpleObject = new SimpleObject(2L);
161162

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

165182
private static void serializeAndDeserializeNullValue(GenericJackson2JsonRedisSerializer serializer) {
@@ -217,6 +234,7 @@ public boolean equals(Object obj) {
217234
@Data
218235
static final class FinalObject {
219236
public Long longValue;
237+
public int[] myArray;
220238
SimpleObject simpleObject;
221239
}
222240

@@ -252,4 +270,11 @@ public boolean equals(Object obj) {
252270
}
253271
}
254272

273+
@Data
274+
static class CountAndArray {
275+
276+
private int count;
277+
private int[] available;
278+
}
279+
255280
}

0 commit comments

Comments
 (0)