1616package org .springframework .data .redis .serializer ;
1717
1818import java .io .IOException ;
19+ import java .util .Collections ;
20+ import java .util .function .Supplier ;
1921
2022import org .springframework .cache .support .NullValue ;
23+ import org .springframework .data .util .Lazy ;
2124import org .springframework .lang .Nullable ;
2225import org .springframework .util .Assert ;
2326import org .springframework .util .StringUtils ;
2427
2528import com .fasterxml .jackson .annotation .JsonTypeInfo ;
2629import com .fasterxml .jackson .annotation .JsonTypeInfo .As ;
2730import com .fasterxml .jackson .core .JsonGenerator ;
31+ import com .fasterxml .jackson .databind .JavaType ;
32+ import com .fasterxml .jackson .databind .JsonNode ;
2833import com .fasterxml .jackson .databind .ObjectMapper ;
2934import com .fasterxml .jackson .databind .ObjectMapper .DefaultTyping ;
35+ import com .fasterxml .jackson .databind .ObjectReader ;
3036import com .fasterxml .jackson .databind .SerializerProvider ;
3137import com .fasterxml .jackson .databind .jsontype .PolymorphicTypeValidator ;
3238import com .fasterxml .jackson .databind .jsontype .TypeSerializer ;
3339import com .fasterxml .jackson .databind .module .SimpleModule ;
40+ import com .fasterxml .jackson .databind .node .TextNode ;
3441import com .fasterxml .jackson .databind .ser .SerializerFactory ;
3542import com .fasterxml .jackson .databind .ser .std .StdSerializer ;
43+ import com .fasterxml .jackson .databind .type .TypeFactory ;
3644
3745/**
3846 * Generic Jackson 2-based {@link RedisSerializer} that maps {@link Object objects} to JSON using dynamic typing.
4755 */
4856public class GenericJackson2JsonRedisSerializer implements RedisSerializer <Object > {
4957
50- private final ObjectMapper mapper ;
58+ private ObjectMapper mapper ;
5159
5260 private final JacksonObjectReader reader ;
5361
5462 private final JacksonObjectWriter writer ;
5563
64+ private boolean internalReader = false ;
65+
66+ private final TypeResolver typeResolver ;
67+
68+ private Lazy <Boolean > defaultTypingEnabled = Lazy
69+ .of (() -> mapper .getSerializationConfig ().getDefaultTyper (null ) != null );
70+
71+ private Lazy <String > typeHintPropertyName ;
72+
73+ {
74+ typeHintPropertyName = Lazy .of (() -> {
75+ if (defaultTypingEnabled .get ()) {
76+ return null ;
77+ }
78+
79+ return mapper .getDeserializationConfig ().getDefaultTyper (null )
80+ .buildTypeDeserializer (mapper .getDeserializationConfig (), mapper .getTypeFactory ().constructType (Object .class ),
81+ Collections .emptyList ())
82+ .getPropertyName ();
83+
84+ }).or ("@class" );
85+
86+ typeResolver = new TypeResolver (Lazy .of (() -> mapper .getTypeFactory ()), typeHintPropertyName );
87+ }
88+
5689 /**
5790 * Creates {@link GenericJackson2JsonRedisSerializer} and configures {@link ObjectMapper} for default typing.
5891 */
@@ -71,6 +104,7 @@ public GenericJackson2JsonRedisSerializer() {
71104 */
72105 public GenericJackson2JsonRedisSerializer (@ Nullable String classPropertyTypeName ) {
73106 this (classPropertyTypeName , JacksonObjectReader .create (), JacksonObjectWriter .create ());
107+ this .internalReader = true ;
74108 }
75109
76110 /**
@@ -100,6 +134,10 @@ public GenericJackson2JsonRedisSerializer(@Nullable String classPropertyTypeName
100134 } else {
101135 mapper .activateDefaultTyping (mapper .getPolymorphicTypeValidator (), DefaultTyping .EVERYTHING , As .PROPERTY );
102136 }
137+
138+ if (classPropertyTypeName != null ) {
139+ typeHintPropertyName = Lazy .of (classPropertyTypeName );
140+ }
103141 }
104142
105143 /**
@@ -111,6 +149,7 @@ public GenericJackson2JsonRedisSerializer(@Nullable String classPropertyTypeName
111149 */
112150 public GenericJackson2JsonRedisSerializer (ObjectMapper mapper ) {
113151 this (mapper , JacksonObjectReader .create (), JacksonObjectWriter .create ());
152+ this .internalReader = true ;
114153 }
115154
116155 /**
@@ -188,12 +227,54 @@ public <T> T deserialize(@Nullable byte[] source, Class<T> type) throws Serializ
188227 }
189228
190229 try {
191- return (T ) reader .read (mapper , source , mapper . getTypeFactory (). constructType ( type ));
230+ return (T ) reader .read (mapper , source , resolveType ( source , type ));
192231 } catch (Exception ex ) {
193232 throw new SerializationException ("Could not read JSON: " + ex .getMessage (), ex );
194233 }
195234 }
196235
236+ protected JavaType resolveType (byte [] source , Class <?> type ) {
237+
238+ if (internalReader || !type .equals (Object .class ) || !defaultTypingEnabled .get ()) {
239+ return typeResolver .constructType (type );
240+ }
241+
242+ return typeResolver .resolveType (source , type );
243+ }
244+
245+ private static class TypeResolver {
246+
247+ private final ObjectReader objectReader = new ObjectMapper ().reader ();
248+
249+ private final Supplier <TypeFactory > typeFactory ;
250+ private Supplier <String > hintName ;
251+
252+ public TypeResolver (Supplier <TypeFactory > typeFactory , Supplier <String > hintName ) {
253+
254+ this .typeFactory = typeFactory ;
255+ this .hintName = hintName ;
256+ }
257+
258+ protected JavaType constructType (Class <?> type ) {
259+ return typeFactory .get ().constructType (type );
260+ }
261+
262+ protected JavaType resolveType (byte [] source , Class <?> type ) {
263+
264+ try {
265+ TextNode typeName = (TextNode ) objectReader .readValue (source , JsonNode .class ).get (hintName .get ());
266+ if (typeName != null ) {
267+ return typeFactory .get ().constructFromCanonical (typeName .textValue ());
268+ }
269+ } catch (IOException e ) {
270+ // TODO: logging?
271+ }
272+
273+ return constructType (type );
274+ }
275+
276+ }
277+
197278 /**
198279 * {@link StdSerializer} adding class information required by default typing. This allows de-/serialization of
199280 * {@link NullValue}.
0 commit comments