16
16
package org .springframework .data .redis .serializer ;
17
17
18
18
import java .io .IOException ;
19
+ import java .util .Collections ;
20
+ import java .util .function .Supplier ;
19
21
20
22
import org .springframework .cache .support .NullValue ;
23
+ import org .springframework .data .util .Lazy ;
21
24
import org .springframework .lang .Nullable ;
22
25
import org .springframework .util .Assert ;
23
26
import org .springframework .util .StringUtils ;
24
27
25
28
import com .fasterxml .jackson .annotation .JsonTypeInfo ;
26
29
import com .fasterxml .jackson .annotation .JsonTypeInfo .As ;
27
30
import com .fasterxml .jackson .core .JsonGenerator ;
31
+ import com .fasterxml .jackson .databind .JavaType ;
32
+ import com .fasterxml .jackson .databind .JsonNode ;
28
33
import com .fasterxml .jackson .databind .ObjectMapper ;
29
34
import com .fasterxml .jackson .databind .ObjectMapper .DefaultTyping ;
35
+ import com .fasterxml .jackson .databind .ObjectReader ;
30
36
import com .fasterxml .jackson .databind .SerializerProvider ;
31
37
import com .fasterxml .jackson .databind .jsontype .PolymorphicTypeValidator ;
32
38
import com .fasterxml .jackson .databind .jsontype .TypeSerializer ;
33
39
import com .fasterxml .jackson .databind .module .SimpleModule ;
40
+ import com .fasterxml .jackson .databind .node .TextNode ;
34
41
import com .fasterxml .jackson .databind .ser .SerializerFactory ;
35
42
import com .fasterxml .jackson .databind .ser .std .StdSerializer ;
43
+ import com .fasterxml .jackson .databind .type .TypeFactory ;
36
44
37
45
/**
38
46
* Generic Jackson 2-based {@link RedisSerializer} that maps {@link Object objects} to JSON using dynamic typing.
47
55
*/
48
56
public class GenericJackson2JsonRedisSerializer implements RedisSerializer <Object > {
49
57
50
- private final ObjectMapper mapper ;
58
+ private ObjectMapper mapper ;
51
59
52
60
private final JacksonObjectReader reader ;
53
61
54
62
private final JacksonObjectWriter writer ;
55
63
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
+
56
89
/**
57
90
* Creates {@link GenericJackson2JsonRedisSerializer} and configures {@link ObjectMapper} for default typing.
58
91
*/
@@ -71,6 +104,7 @@ public GenericJackson2JsonRedisSerializer() {
71
104
*/
72
105
public GenericJackson2JsonRedisSerializer (@ Nullable String classPropertyTypeName ) {
73
106
this (classPropertyTypeName , JacksonObjectReader .create (), JacksonObjectWriter .create ());
107
+ this .internalReader = true ;
74
108
}
75
109
76
110
/**
@@ -100,6 +134,10 @@ public GenericJackson2JsonRedisSerializer(@Nullable String classPropertyTypeName
100
134
} else {
101
135
mapper .activateDefaultTyping (mapper .getPolymorphicTypeValidator (), DefaultTyping .EVERYTHING , As .PROPERTY );
102
136
}
137
+
138
+ if (classPropertyTypeName != null ) {
139
+ typeHintPropertyName = Lazy .of (classPropertyTypeName );
140
+ }
103
141
}
104
142
105
143
/**
@@ -111,6 +149,7 @@ public GenericJackson2JsonRedisSerializer(@Nullable String classPropertyTypeName
111
149
*/
112
150
public GenericJackson2JsonRedisSerializer (ObjectMapper mapper ) {
113
151
this (mapper , JacksonObjectReader .create (), JacksonObjectWriter .create ());
152
+ this .internalReader = true ;
114
153
}
115
154
116
155
/**
@@ -188,12 +227,54 @@ public <T> T deserialize(@Nullable byte[] source, Class<T> type) throws Serializ
188
227
}
189
228
190
229
try {
191
- return (T ) reader .read (mapper , source , mapper . getTypeFactory (). constructType ( type ));
230
+ return (T ) reader .read (mapper , source , resolveType ( source , type ));
192
231
} catch (Exception ex ) {
193
232
throw new SerializationException ("Could not read JSON: " + ex .getMessage (), ex );
194
233
}
195
234
}
196
235
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
+
197
278
/**
198
279
* {@link StdSerializer} adding class information required by default typing. This allows de-/serialization of
199
280
* {@link NullValue}.
0 commit comments