1717
1818import java .io .IOException ;
1919import java .util .Collections ;
20+ import java .util .Map ;
2021import java .util .function .Supplier ;
2122
2223import org .springframework .cache .support .NullValue ;
2930import com .fasterxml .jackson .annotation .JsonTypeInfo .As ;
3031import com .fasterxml .jackson .core .JsonGenerator ;
3132import com .fasterxml .jackson .databind .JavaType ;
32- import com .fasterxml .jackson .databind .JsonNode ;
3333import com .fasterxml .jackson .databind .ObjectMapper ;
3434import com .fasterxml .jackson .databind .ObjectMapper .DefaultTyping ;
35- import com .fasterxml .jackson .databind .ObjectReader ;
3635import com .fasterxml .jackson .databind .SerializerProvider ;
3736import com .fasterxml .jackson .databind .jsontype .PolymorphicTypeValidator ;
3837import com .fasterxml .jackson .databind .jsontype .TypeSerializer ;
3938import com .fasterxml .jackson .databind .module .SimpleModule ;
40- import com .fasterxml .jackson .databind .node .TextNode ;
4139import com .fasterxml .jackson .databind .ser .SerializerFactory ;
4240import com .fasterxml .jackson .databind .ser .std .StdSerializer ;
4341import com .fasterxml .jackson .databind .type .TypeFactory ;
5553 */
5654public class GenericJackson2JsonRedisSerializer implements RedisSerializer <Object > {
5755
58- private ObjectMapper mapper ;
56+ private final ObjectMapper mapper ;
5957
6058 private final JacksonObjectReader reader ;
6159
6260 private final JacksonObjectWriter writer ;
6361
64- private boolean internalReader = false ;
62+ private final Lazy < Boolean > defaultTypingEnabled ;
6563
6664 private final TypeResolver typeResolver ;
6765
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-
8966 /**
9067 * Creates {@link GenericJackson2JsonRedisSerializer} and configures {@link ObjectMapper} for default typing.
9168 */
@@ -104,7 +81,6 @@ public GenericJackson2JsonRedisSerializer() {
10481 */
10582 public GenericJackson2JsonRedisSerializer (@ Nullable String classPropertyTypeName ) {
10683 this (classPropertyTypeName , JacksonObjectReader .create (), JacksonObjectWriter .create ());
107- this .internalReader = true ;
10884 }
10985
11086 /**
@@ -122,7 +98,7 @@ public GenericJackson2JsonRedisSerializer(@Nullable String classPropertyTypeName
12298 public GenericJackson2JsonRedisSerializer (@ Nullable String classPropertyTypeName , JacksonObjectReader reader ,
12399 JacksonObjectWriter writer ) {
124100
125- this (new ObjectMapper (), reader , writer );
101+ this (new ObjectMapper (), reader , writer , classPropertyTypeName );
126102
127103 // simply setting {@code mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)} does not help here since we need
128104 // the type hint embedded for deserialization using the default typing feature.
@@ -134,10 +110,6 @@ public GenericJackson2JsonRedisSerializer(@Nullable String classPropertyTypeName
134110 } else {
135111 mapper .activateDefaultTyping (mapper .getPolymorphicTypeValidator (), DefaultTyping .EVERYTHING , As .PROPERTY );
136112 }
137-
138- if (classPropertyTypeName != null ) {
139- typeHintPropertyName = Lazy .of (classPropertyTypeName );
140- }
141113 }
142114
143115 /**
@@ -149,7 +121,6 @@ public GenericJackson2JsonRedisSerializer(@Nullable String classPropertyTypeName
149121 */
150122 public GenericJackson2JsonRedisSerializer (ObjectMapper mapper ) {
151123 this (mapper , JacksonObjectReader .create (), JacksonObjectWriter .create ());
152- this .internalReader = true ;
153124 }
154125
155126 /**
@@ -164,6 +135,11 @@ public GenericJackson2JsonRedisSerializer(ObjectMapper mapper) {
164135 */
165136 public GenericJackson2JsonRedisSerializer (ObjectMapper mapper , JacksonObjectReader reader ,
166137 JacksonObjectWriter writer ) {
138+ this (mapper , reader , writer , null );
139+ }
140+
141+ private GenericJackson2JsonRedisSerializer (ObjectMapper mapper , JacksonObjectReader reader ,
142+ JacksonObjectWriter writer , @ Nullable String typeHintPropertyName ) {
167143
168144 Assert .notNull (mapper , "ObjectMapper must not be null!" );
169145 Assert .notNull (reader , "Reader must not be null!" );
@@ -172,6 +148,29 @@ public GenericJackson2JsonRedisSerializer(ObjectMapper mapper, JacksonObjectRead
172148 this .mapper = mapper ;
173149 this .reader = reader ;
174150 this .writer = writer ;
151+
152+ this .defaultTypingEnabled = Lazy .of (() -> mapper .getSerializationConfig ().getDefaultTyper (null ) != null );
153+
154+ Supplier <String > typeHintPropertyNameSupplier ;
155+
156+ if (typeHintPropertyName == null ) {
157+
158+ typeHintPropertyNameSupplier = Lazy .of (() -> {
159+ if (defaultTypingEnabled .get ()) {
160+ return null ;
161+ }
162+
163+ return mapper .getDeserializationConfig ().getDefaultTyper (null )
164+ .buildTypeDeserializer (mapper .getDeserializationConfig (),
165+ mapper .getTypeFactory ().constructType (Object .class ), Collections .emptyList ())
166+ .getPropertyName ();
167+
168+ }).or ("@class" );
169+ } else {
170+ typeHintPropertyNameSupplier = () -> typeHintPropertyName ;
171+ }
172+
173+ this .typeResolver = new TypeResolver (Lazy .of (mapper ::getTypeFactory ), typeHintPropertyNameSupplier );
175174 }
176175
177176 /**
@@ -233,21 +232,22 @@ public <T> T deserialize(@Nullable byte[] source, Class<T> type) throws Serializ
233232 }
234233 }
235234
236- protected JavaType resolveType (byte [] source , Class <?> type ) {
235+ protected JavaType resolveType (byte [] source , Class <?> type ) throws IOException {
237236
238- if (internalReader || !type .equals (Object .class ) || !defaultTypingEnabled .get ()) {
237+ if (!type .equals (Object .class ) || !defaultTypingEnabled .get ()) {
239238 return typeResolver .constructType (type );
240239 }
241240
242241 return typeResolver .resolveType (source , type );
243242 }
244243
245- private static class TypeResolver {
244+ static class TypeResolver {
246245
247- private final ObjectReader objectReader = new ObjectMapper ().reader ();
246+ // need a separate instance to bypass class hint checks
247+ private final ObjectMapper mapper = new ObjectMapper ();
248248
249249 private final Supplier <TypeFactory > typeFactory ;
250- private Supplier <String > hintName ;
250+ private final Supplier <String > hintName ;
251251
252252 public TypeResolver (Supplier <TypeFactory > typeFactory , Supplier <String > hintName ) {
253253
@@ -259,15 +259,13 @@ protected JavaType constructType(Class<?> type) {
259259 return typeFactory .get ().constructType (type );
260260 }
261261
262- protected JavaType resolveType (byte [] source , Class <?> type ) {
262+ protected JavaType resolveType (byte [] source , Class <?> type ) throws IOException {
263263
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?
264+ Map <?, ?> map = mapper .readValue (source , Map .class );
265+ Object typeName = map .get (hintName .get ());
266+
267+ if (typeName != null ) {
268+ return typeFactory .get ().constructFromCanonical (typeName .toString ());
271269 }
272270
273271 return constructType (type );
0 commit comments