16
16
package org .springframework .data .redis .serializer ;
17
17
18
18
import java .io .IOException ;
19
+ import java .io .Serial ;
19
20
import java .util .Collections ;
20
21
import java .util .function .Supplier ;
21
22
22
23
import org .springframework .cache .support .NullValue ;
23
24
import org .springframework .core .KotlinDetector ;
25
+ import org .springframework .data .redis .util .RedisAssertions ;
24
26
import org .springframework .data .util .Lazy ;
25
27
import org .springframework .lang .Nullable ;
26
28
import org .springframework .util .Assert ;
46
48
import com .fasterxml .jackson .databind .type .TypeFactory ;
47
49
48
50
/**
49
- * Generic Jackson 2-based {@link RedisSerializer} that maps {@link Object objects} to JSON using dynamic typing.
51
+ * Generic Jackson 2-based {@link RedisSerializer} that maps {@link Object objects} to and from {@literal JSON}
52
+ * using dynamic typing.
50
53
* <p>
51
- * JSON reading and writing can be customized by configuring {@link JacksonObjectReader} respective
52
- * {@link JacksonObjectWriter}.
54
+ * {@literal JSON} reading and writing can be customized by configuring a {@link JacksonObjectReader}
55
+ * and {@link JacksonObjectWriter}.
53
56
*
54
57
* @author Christoph Strobl
55
58
* @author Mark Paluch
56
59
* @author Mao Shuai
60
+ * @author John Blum
61
+ * @see org.springframework.data.redis.serializer.JacksonObjectReader
62
+ * @see org.springframework.data.redis.serializer.JacksonObjectWriter
63
+ * @see com.fasterxml.jackson.databind.ObjectMapper
57
64
* @since 1.6
58
65
*/
59
66
public class GenericJackson2JsonRedisSerializer implements RedisSerializer <Object > {
60
67
61
- private final ObjectMapper mapper ;
68
+ /**
69
+ * Register {@link NullValueSerializer} in the given {@link ObjectMapper} with an optional
70
+ * {@code classPropertyTypeName}. This method should be called by code that customizes
71
+ * {@link GenericJackson2JsonRedisSerializer} by providing an external {@link ObjectMapper}.
72
+ *
73
+ * @param objectMapper the object mapper to customize.
74
+ * @param classPropertyTypeName name of the type property. Defaults to {@code @class} if {@literal null}/empty.
75
+ * @since 2.2
76
+ */
77
+ public static void registerNullValueSerializer (ObjectMapper objectMapper , @ Nullable String classPropertyTypeName ) {
78
+
79
+ // Simply setting {@code mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)} does not help here
80
+ // since we need the type hint embedded for deserialization using the default typing feature.
81
+ objectMapper .registerModule (new SimpleModule ().addSerializer (new NullValueSerializer (classPropertyTypeName )));
82
+ }
62
83
63
84
private final JacksonObjectReader reader ;
64
85
65
86
private final JacksonObjectWriter writer ;
66
87
67
88
private final Lazy <Boolean > defaultTypingEnabled ;
68
89
90
+ private final ObjectMapper mapper ;
91
+
69
92
private final TypeResolver typeResolver ;
70
93
71
94
/**
72
- * Creates {@link GenericJackson2JsonRedisSerializer} and configures {@link ObjectMapper} for default typing.
95
+ * Creates {@link GenericJackson2JsonRedisSerializer} initialized with an {@link ObjectMapper} configured for
96
+ * default typing.
73
97
*/
74
98
public GenericJackson2JsonRedisSerializer () {
75
99
this ((String ) null );
76
100
}
77
101
78
102
/**
79
- * Creates {@link GenericJackson2JsonRedisSerializer} and configures {@link ObjectMapper} for default typing using the
80
- * given {@literal name}. In case of an {@literal empty} or {@literal null} String the default
81
- * {@link JsonTypeInfo.Id#CLASS} will be used.
103
+ * Creates {@link GenericJackson2JsonRedisSerializer} initialized with an {@link ObjectMapper} configured for
104
+ * default typing using the given {@link String name}.
105
+ * <p>
106
+ * In case {@link String name} is {@literal empty} or {@literal null}, then {@link JsonTypeInfo.Id#CLASS}
107
+ * will be used.
82
108
*
83
- * @param classPropertyTypeName name of the JSON property holding type information. Can be {@literal null}.
109
+ * @param classPropertyTypeName {@link String name} of the JSON property holding type information;
110
+ * can be {@literal null}.
84
111
* @see ObjectMapper#activateDefaultTypingAsProperty(PolymorphicTypeValidator, DefaultTyping, String)
85
112
* @see ObjectMapper#activateDefaultTyping(PolymorphicTypeValidator, DefaultTyping, As)
86
113
*/
@@ -89,13 +116,17 @@ public GenericJackson2JsonRedisSerializer(@Nullable String classPropertyTypeName
89
116
}
90
117
91
118
/**
92
- * Creates {@link GenericJackson2JsonRedisSerializer} and configures {@link ObjectMapper} for default typing using the
93
- * given {@literal name}. In case of an {@literal empty} or {@literal null} String the default
94
- * {@link JsonTypeInfo.Id#CLASS} will be used.
119
+ * Creates {@link GenericJackson2JsonRedisSerializer} initialized with an {@link ObjectMapper} configured for
120
+ * default typing using the given {@link String name} along with the given, required {@link JacksonObjectReader}
121
+ * and {@link JacksonObjectWriter} used to read/write {@link Object Objects} de/serialized as JSON.
122
+ * <p>
123
+ * In case {@link String name} is {@literal empty} or {@literal null}, then {@link JsonTypeInfo.Id#CLASS}
124
+ * will be used.
95
125
*
96
- * @param classPropertyTypeName name of the JSON property holding type information. Can be {@literal null}.
97
- * @param reader the {@link JacksonObjectReader} function to read objects using {@link ObjectMapper}.
98
- * @param writer the {@link JacksonObjectWriter} function to write objects using {@link ObjectMapper}.
126
+ * @param classPropertyTypeName {@link String name} of the JSON property holding type information;
127
+ * can be {@literal null}.
128
+ * @param reader {@link JacksonObjectReader} function to read objects using {@link ObjectMapper}.
129
+ * @param writer {@link JacksonObjectWriter} function to write objects using {@link ObjectMapper}.
99
130
* @see ObjectMapper#activateDefaultTypingAsProperty(PolymorphicTypeValidator, DefaultTyping, String)
100
131
* @see ObjectMapper#activateDefaultTyping(PolymorphicTypeValidator, DefaultTyping, As)
101
132
* @since 3.0
@@ -105,19 +136,17 @@ public GenericJackson2JsonRedisSerializer(@Nullable String classPropertyTypeName
105
136
106
137
this (new ObjectMapper (), reader , writer , classPropertyTypeName );
107
138
108
- // simply setting {@code mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)} does not help here since we need
109
- // the type hint embedded for deserialization using the default typing feature.
110
- registerNullValueSerializer (mapper , classPropertyTypeName );
139
+ registerNullValueSerializer (this .mapper , classPropertyTypeName );
111
140
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 );
141
+ StdTypeResolverBuilder typer = TypeResolverBuilder .forEverything (this .mapper )
142
+ .init (JsonTypeInfo .Id .CLASS , null )
143
+ .inclusion (JsonTypeInfo .As .PROPERTY );
116
144
117
145
if (StringUtils .hasText (classPropertyTypeName )) {
118
146
typer = typer .typeProperty (classPropertyTypeName );
119
147
}
120
- mapper .setDefaultTyping (typer );
148
+
149
+ this .mapper .setDefaultTyping (typer );
121
150
}
122
151
123
152
/**
@@ -143,58 +172,34 @@ public GenericJackson2JsonRedisSerializer(ObjectMapper mapper) {
143
172
*/
144
173
public GenericJackson2JsonRedisSerializer (ObjectMapper mapper , JacksonObjectReader reader ,
145
174
JacksonObjectWriter writer ) {
175
+
146
176
this (mapper , reader , writer , null );
147
177
}
148
178
149
179
private GenericJackson2JsonRedisSerializer (ObjectMapper mapper , JacksonObjectReader reader ,
150
180
JacksonObjectWriter writer , @ Nullable String typeHintPropertyName ) {
151
181
152
- Assert .notNull (mapper , "ObjectMapper must not be null" );
153
- Assert .notNull (reader , "Reader must not be null" );
154
- Assert .notNull (writer , "Writer must not be null" );
155
-
156
- this .mapper = mapper ;
157
- this .reader = reader ;
158
- this .writer = writer ;
182
+ this .mapper = RedisAssertions .requireObject (mapper , "ObjectMapper must not be null" );
183
+ this .reader = RedisAssertions .requireObject (reader , "Reader must not be null" );
184
+ this .writer = RedisAssertions .requireObject (writer , "Writer must not be null" );
159
185
160
- this .defaultTypingEnabled = Lazy .of (() -> mapper .getSerializationConfig ().getDefaultTyper (null ) != null );
186
+ this .defaultTypingEnabled = Lazy .of (() -> mapper .getSerializationConfig ()
187
+ .getDefaultTyper (null ) != null );
161
188
162
- Supplier < String > typeHintPropertyNameSupplier ;
163
-
164
- if ( typeHintPropertyName == null ) {
189
+ this . typeResolver = new TypeResolver ( Lazy . of ( mapper :: getTypeFactory ),
190
+ newTypeHintPropertyNameSupplier ( mapper , typeHintPropertyName , this . defaultTypingEnabled ));
191
+ }
165
192
166
- typeHintPropertyNameSupplier = Lazy .of (() -> {
167
- if (defaultTypingEnabled .get ()) {
168
- return null ;
169
- }
193
+ private Supplier <String > newTypeHintPropertyNameSupplier (ObjectMapper mapper , @ Nullable String typeHintPropertyName ,
194
+ Lazy <Boolean > defaultTypingEnabled ) {
170
195
171
- return mapper .getDeserializationConfig ().getDefaultTyper (null )
196
+ return typeHintPropertyName != null ? () -> typeHintPropertyName
197
+ : Lazy .of (() -> defaultTypingEnabled .get () ? null
198
+ : mapper .getDeserializationConfig ().getDefaultTyper (null )
172
199
.buildTypeDeserializer (mapper .getDeserializationConfig (),
173
200
mapper .getTypeFactory ().constructType (Object .class ), Collections .emptyList ())
174
- .getPropertyName ();
175
-
176
- }).or ("@class" );
177
- } else {
178
- typeHintPropertyNameSupplier = () -> typeHintPropertyName ;
179
- }
180
-
181
- this .typeResolver = new TypeResolver (Lazy .of (mapper ::getTypeFactory ), typeHintPropertyNameSupplier );
182
- }
183
-
184
- /**
185
- * Register {@link NullValueSerializer} in the given {@link ObjectMapper} with an optional
186
- * {@code classPropertyTypeName}. This method should be called by code that customizes
187
- * {@link GenericJackson2JsonRedisSerializer} by providing an external {@link ObjectMapper}.
188
- *
189
- * @param objectMapper the object mapper to customize.
190
- * @param classPropertyTypeName name of the type property. Defaults to {@code @class} if {@literal null}/empty.
191
- * @since 2.2
192
- */
193
- public static void registerNullValueSerializer (ObjectMapper objectMapper , @ Nullable String classPropertyTypeName ) {
194
-
195
- // simply setting {@code mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)} does not help here since we need
196
- // the type hint embedded for deserialization using the default typing feature.
197
- objectMapper .registerModule (new SimpleModule ().addSerializer (new NullValueSerializer (classPropertyTypeName )));
201
+ .getPropertyName ())
202
+ .or ("@class" );
198
203
}
199
204
200
205
@ Override
@@ -206,8 +211,9 @@ public byte[] serialize(@Nullable Object source) throws SerializationException {
206
211
207
212
try {
208
213
return writer .write (mapper , source );
209
- } catch (IOException e ) {
210
- throw new SerializationException ("Could not write JSON: " + e .getMessage (), e );
214
+ } catch (IOException cause ) {
215
+ String message = String .format ("Could not write JSON: %s" , cause .getMessage ());
216
+ throw new SerializationException (message , cause );
211
217
}
212
218
}
213
219
@@ -217,17 +223,24 @@ public Object deserialize(@Nullable byte[] source) throws SerializationException
217
223
}
218
224
219
225
/**
220
- * @param source can be {@literal null}.
221
- * @param type must not be {@literal null}.
222
- * @return {@literal null} for empty source.
223
- * @throws SerializationException
226
+ * Deserialized the array of bytes containing {@literal JSON} as an {@link Object} of the given,
227
+ * required {@link Class type}.
228
+ *
229
+ * @param source array of bytes containing the {@literal JSON} to deserialize; can be {@literal null}.
230
+ * @param type {@link Class type} of {@link Object} from which the {@literal JSON} will be deserialized;
231
+ * must not be {@literal null}.
232
+ * @return {@literal null} for an empty source, or an {@link Object} of the given {@link Class type}
233
+ * deserialized from the array of bytes containing {@literal JSON}.
234
+ * @throws IllegalArgumentException if the given {@link Class type} is {@literal null}.
235
+ * @throws SerializationException if the array of bytes cannot be deserialized as an instance of
236
+ * the given {@link Class type}
224
237
*/
225
238
@ Nullable
226
239
@ SuppressWarnings ("unchecked" )
227
240
public <T > T deserialize (@ Nullable byte [] source , Class <T > type ) throws SerializationException {
228
241
229
- Assert .notNull (type ,
230
- "Deserialization type must not be null Please provide Object.class to make use of Jackson2 default typing." );
242
+ Assert .notNull (type , "Deserialization type must not be null;"
243
+ + " Please provide Object.class to make use of Jackson2 default typing." );
231
244
232
245
if (SerializationUtils .isEmpty (source )) {
233
246
return null ;
@@ -292,7 +305,9 @@ protected JavaType resolveType(byte[] source, Class<?> type) throws IOException
292
305
*/
293
306
private static class NullValueSerializer extends StdSerializer <NullValue > {
294
307
308
+ @ Serial
295
309
private static final long serialVersionUID = 1999052150548658808L ;
310
+
296
311
private final String classIdentifier ;
297
312
298
313
/**
@@ -305,17 +320,19 @@ private static class NullValueSerializer extends StdSerializer<NullValue> {
305
320
}
306
321
307
322
@ Override
308
- public void serialize (NullValue value , JsonGenerator jgen , SerializerProvider provider ) throws IOException {
323
+ public void serialize (NullValue value , JsonGenerator jsonGenerator , SerializerProvider provider )
324
+ throws IOException {
309
325
310
- jgen .writeStartObject ();
311
- jgen .writeStringField (classIdentifier , NullValue .class .getName ());
312
- jgen .writeEndObject ();
326
+ jsonGenerator .writeStartObject ();
327
+ jsonGenerator .writeStringField (classIdentifier , NullValue .class .getName ());
328
+ jsonGenerator .writeEndObject ();
313
329
}
314
330
315
331
@ Override
316
- public void serializeWithType (NullValue value , JsonGenerator gen , SerializerProvider serializers ,
317
- TypeSerializer typeSer ) throws IOException {
318
- serialize (value , gen , serializers );
332
+ public void serializeWithType (NullValue value , JsonGenerator jsonGenerator , SerializerProvider serializers ,
333
+ TypeSerializer typeSerializer ) throws IOException {
334
+
335
+ serialize (value , jsonGenerator , serializers );
319
336
}
320
337
}
321
338
@@ -329,8 +346,12 @@ public void serializeWithType(NullValue value, JsonGenerator gen, SerializerProv
329
346
*/
330
347
private static class TypeResolverBuilder extends ObjectMapper .DefaultTypeResolverBuilder {
331
348
332
- public TypeResolverBuilder (DefaultTyping t , PolymorphicTypeValidator ptv ) {
333
- super (t , ptv );
349
+ static TypeResolverBuilder forEverything (ObjectMapper mapper ) {
350
+ return new TypeResolverBuilder (DefaultTyping .EVERYTHING , mapper .getPolymorphicTypeValidator ());
351
+ }
352
+
353
+ public TypeResolverBuilder (DefaultTyping typing , PolymorphicTypeValidator polymorphicTypeValidator ) {
354
+ super (typing , polymorphicTypeValidator );
334
355
}
335
356
336
357
@ Override
@@ -343,25 +364,25 @@ public ObjectMapper.DefaultTypeResolverBuilder withDefaultImpl(Class<?> defaultI
343
364
* Boolean, Integer, Double) will never use typing; that is both due to them being concrete and final, and since
344
365
* actual serializers and deserializers will also ignore any attempts to enforce typing.
345
366
*/
346
- public boolean useForType (JavaType t ) {
367
+ public boolean useForType (JavaType javaType ) {
347
368
348
- if (t .isJavaLangObject ()) {
369
+ if (javaType .isJavaLangObject ()) {
349
370
return true ;
350
371
}
351
372
352
- t = resolveArrayOrWrapper (t );
373
+ javaType = resolveArrayOrWrapper (javaType );
353
374
354
- if (t .isEnumType () || ClassUtils .isPrimitiveOrWrapper (t .getRawClass ())) {
375
+ if (javaType .isEnumType () || ClassUtils .isPrimitiveOrWrapper (javaType .getRawClass ())) {
355
376
return false ;
356
377
}
357
378
358
- if (t .isFinal () && !KotlinDetector .isKotlinType (t .getRawClass ())
359
- && t .getRawClass ().getPackageName ().startsWith ("java" )) {
379
+ if (javaType .isFinal () && !KotlinDetector .isKotlinType (javaType .getRawClass ())
380
+ && javaType .getRawClass ().getPackageName ().startsWith ("java" )) {
360
381
return false ;
361
382
}
362
383
363
384
// [databind#88] Should not apply to JSON tree models:
364
- return !TreeNode .class .isAssignableFrom (t .getRawClass ());
385
+ return !TreeNode .class .isAssignableFrom (javaType .getRawClass ());
365
386
}
366
387
367
388
private JavaType resolveArrayOrWrapper (JavaType type ) {
0 commit comments