-
Notifications
You must be signed in to change notification settings - Fork 1.2k
/
GenericJackson2JsonRedisSerializer.java
634 lines (510 loc) · 23.4 KB
/
GenericJackson2JsonRedisSerializer.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
/*
* Copyright 2015-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.redis.serializer;
import java.io.IOException;
import java.io.Serial;
import java.util.Collections;
import java.util.function.Consumer;
import java.util.function.Supplier;
import org.springframework.cache.support.NullValue;
import org.springframework.core.KotlinDetector;
import org.springframework.data.util.Lazy;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.TreeNode;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator;
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
import com.fasterxml.jackson.databind.jsontype.impl.StdTypeResolverBuilder;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.node.TextNode;
import com.fasterxml.jackson.databind.ser.SerializerFactory;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import com.fasterxml.jackson.databind.type.TypeFactory;
/**
* Generic Jackson 2-based {@link RedisSerializer} that maps {@link Object objects} to and from {@literal JSON} using
* dynamic typing.
* <p>
* {@literal JSON} reading and writing can be customized by configuring a {@link JacksonObjectReader} and
* {@link JacksonObjectWriter}.
*
* @author Christoph Strobl
* @author Mark Paluch
* @author Mao Shuai
* @author John Blum
* @author Anne Lee
* @see org.springframework.data.redis.serializer.JacksonObjectReader
* @see org.springframework.data.redis.serializer.JacksonObjectWriter
* @see com.fasterxml.jackson.databind.ObjectMapper
* @since 1.6
*/
public class GenericJackson2JsonRedisSerializer implements RedisSerializer<Object> {
private final JacksonObjectReader reader;
private final JacksonObjectWriter writer;
private final Lazy<Boolean> defaultTypingEnabled;
private final ObjectMapper mapper;
private final TypeResolver typeResolver;
/**
* Creates {@link GenericJackson2JsonRedisSerializer} initialized with an {@link ObjectMapper} configured for default
* typing.
*/
public GenericJackson2JsonRedisSerializer() {
this((String) null);
}
/**
* Creates {@link GenericJackson2JsonRedisSerializer} initialized with an {@link ObjectMapper} configured for default
* typing using the given {@link String name}.
* <p>
* In case {@link String name} is {@literal empty} or {@literal null}, then {@link JsonTypeInfo.Id#CLASS} will be
* used.
*
* @param typeHintPropertyName {@link String name} of the JSON property holding type information; can be
* {@literal null}.
* @see ObjectMapper#activateDefaultTypingAsProperty(PolymorphicTypeValidator, DefaultTyping, String)
* @see ObjectMapper#activateDefaultTyping(PolymorphicTypeValidator, DefaultTyping, As)
*/
public GenericJackson2JsonRedisSerializer(@Nullable String typeHintPropertyName) {
this(typeHintPropertyName, JacksonObjectReader.create(), JacksonObjectWriter.create());
}
/**
* Creates {@link GenericJackson2JsonRedisSerializer} initialized with an {@link ObjectMapper} configured for default
* typing using the given {@link String name} along with the given, required {@link JacksonObjectReader} and
* {@link JacksonObjectWriter} used to read/write {@link Object Objects} de/serialized as JSON.
* <p>
* In case {@link String name} is {@literal empty} or {@literal null}, then {@link JsonTypeInfo.Id#CLASS} will be
* used.
*
* @param typeHintPropertyName {@link String name} of the JSON property holding type information; can be
* {@literal null}.
* @param reader {@link JacksonObjectReader} function to read objects using {@link ObjectMapper}.
* @param writer {@link JacksonObjectWriter} function to write objects using {@link ObjectMapper}.
* @see ObjectMapper#activateDefaultTypingAsProperty(PolymorphicTypeValidator, DefaultTyping, String)
* @see ObjectMapper#activateDefaultTyping(PolymorphicTypeValidator, DefaultTyping, As)
* @since 3.0
*/
public GenericJackson2JsonRedisSerializer(@Nullable String typeHintPropertyName, JacksonObjectReader reader,
JacksonObjectWriter writer) {
this(new ObjectMapper(), reader, writer, typeHintPropertyName);
registerNullValueSerializer(this.mapper, typeHintPropertyName);
this.mapper.setDefaultTyping(createDefaultTypeResolverBuilder(getObjectMapper(), typeHintPropertyName));
}
/**
* Setting a custom-configured {@link ObjectMapper} is one way to take further control of the JSON serialization
* process. For example, an extended {@link SerializerFactory} can be configured that provides custom serializers for
* specific types.
*
* @param mapper must not be {@literal null}.
*/
public GenericJackson2JsonRedisSerializer(ObjectMapper mapper) {
this(mapper, JacksonObjectReader.create(), JacksonObjectWriter.create());
}
/**
* Setting a custom-configured {@link ObjectMapper} is one way to take further control of the JSON serialization
* process. For example, an extended {@link SerializerFactory} can be configured that provides custom serializers for
* specific types.
*
* @param mapper must not be {@literal null}.
* @param reader the {@link JacksonObjectReader} function to read objects using {@link ObjectMapper}.
* @param writer the {@link JacksonObjectWriter} function to write objects using {@link ObjectMapper}.
* @since 3.0
*/
public GenericJackson2JsonRedisSerializer(ObjectMapper mapper, JacksonObjectReader reader,
JacksonObjectWriter writer) {
this(mapper, reader, writer, null);
}
private GenericJackson2JsonRedisSerializer(ObjectMapper mapper, JacksonObjectReader reader,
JacksonObjectWriter writer, @Nullable String typeHintPropertyName) {
Assert.notNull(mapper, "ObjectMapper must not be null");
Assert.notNull(reader, "Reader must not be null");
Assert.notNull(writer, "Writer must not be null");
this.mapper = mapper;
this.reader = reader;
this.writer = writer;
this.defaultTypingEnabled = Lazy.of(() -> mapper.getSerializationConfig().getDefaultTyper(null) != null);
this.typeResolver = newTypeResolver(mapper, typeHintPropertyName, this.defaultTypingEnabled);
}
private static TypeResolver newTypeResolver(ObjectMapper mapper, @Nullable String typeHintPropertyName,
Lazy<Boolean> defaultTypingEnabled) {
Lazy<TypeFactory> lazyTypeFactory = Lazy.of(mapper::getTypeFactory);
Lazy<String> lazyTypeHintPropertyName = typeHintPropertyName != null ? Lazy.of(typeHintPropertyName)
: newLazyTypeHintPropertyName(mapper, defaultTypingEnabled);
return new TypeResolver(lazyTypeFactory, lazyTypeHintPropertyName);
}
private static Lazy<String> newLazyTypeHintPropertyName(ObjectMapper mapper, Lazy<Boolean> defaultTypingEnabled) {
Lazy<String> configuredTypeDeserializationPropertyName = getConfiguredTypeDeserializationPropertyName(mapper);
Lazy<String> resolvedLazyTypeHintPropertyName = Lazy
.of(() -> defaultTypingEnabled.get() ? null : configuredTypeDeserializationPropertyName.get());
return resolvedLazyTypeHintPropertyName.or("@class");
}
private static Lazy<String> getConfiguredTypeDeserializationPropertyName(ObjectMapper mapper) {
return Lazy.of(() -> {
DeserializationConfig deserializationConfig = mapper.getDeserializationConfig();
JavaType objectType = mapper.getTypeFactory().constructType(Object.class);
TypeDeserializer typeDeserializer = deserializationConfig.getDefaultTyper(null)
.buildTypeDeserializer(deserializationConfig, objectType, Collections.emptyList());
return typeDeserializer.getPropertyName();
});
}
private static StdTypeResolverBuilder createDefaultTypeResolverBuilder(ObjectMapper objectMapper,
@Nullable String typeHintPropertyName) {
StdTypeResolverBuilder typer = TypeResolverBuilder.forEverything(objectMapper).init(JsonTypeInfo.Id.CLASS, null)
.inclusion(As.PROPERTY);
if (StringUtils.hasText(typeHintPropertyName)) {
typer = typer.typeProperty(typeHintPropertyName);
}
return typer;
}
/**
* Factory method returning a {@literal Builder} used to construct and configure a
* {@link GenericJackson2JsonRedisSerializer}.
*
* @return new {@link GenericJackson2JsonRedisSerializer.GenericJackson2JsonRedisSerializerBuilder}.
* @since 3.3.1
*/
public static GenericJackson2JsonRedisSerializerBuilder builder() {
return new GenericJackson2JsonRedisSerializerBuilder();
}
/**
* Register {@link NullValueSerializer} in the given {@link ObjectMapper} with an optional
* {@code typeHintPropertyName}. This method should be called by code that customizes
* {@link GenericJackson2JsonRedisSerializer} by providing an external {@link ObjectMapper}.
*
* @param objectMapper the object mapper to customize.
* @param typeHintPropertyName name of the type property. Defaults to {@code @class} if {@literal null}/empty.
* @since 2.2
*/
public static void registerNullValueSerializer(ObjectMapper objectMapper, @Nullable String typeHintPropertyName) {
// Simply setting {@code mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)} does not help here
// since we need the type hint embedded for deserialization using the default typing feature.
objectMapper.registerModule(new SimpleModule().addSerializer(new NullValueSerializer(typeHintPropertyName)));
}
/**
* Gets the configured {@link ObjectMapper} used internally by this {@link GenericJackson2JsonRedisSerializer} to
* de/serialize {@link Object objects} as {@literal JSON}.
*
* @return the configured {@link ObjectMapper}.
*/
protected ObjectMapper getObjectMapper() {
return this.mapper;
}
@Override
public byte[] serialize(@Nullable Object value) throws SerializationException {
if (value == null) {
return SerializationUtils.EMPTY_ARRAY;
}
try {
return writer.write(mapper, value);
} catch (IOException ex) {
String message = String.format("Could not write JSON: %s", ex.getMessage());
throw new SerializationException(message, ex);
}
}
@Override
public Object deserialize(@Nullable byte[] source) throws SerializationException {
return deserialize(source, Object.class);
}
/**
* Deserialized the array of bytes containing {@literal JSON} as an {@link Object} of the given, required {@link Class
* type}.
*
* @param source array of bytes containing the {@literal JSON} to deserialize; can be {@literal null}.
* @param type {@link Class type} of {@link Object} from which the {@literal JSON} will be deserialized; must not be
* {@literal null}.
* @return {@literal null} for an empty source, or an {@link Object} of the given {@link Class type} deserialized from
* the array of bytes containing {@literal JSON}.
* @throws IllegalArgumentException if the given {@link Class type} is {@literal null}.
* @throws SerializationException if the array of bytes cannot be deserialized as an instance of the given
* {@link Class type}
*/
@Nullable
@SuppressWarnings("unchecked")
public <T> T deserialize(@Nullable byte[] source, Class<T> type) throws SerializationException {
Assert.notNull(type, "Deserialization type must not be null;"
+ " Please provide Object.class to make use of Jackson2 default typing.");
if (SerializationUtils.isEmpty(source)) {
return null;
}
try {
return (T) reader.read(mapper, source, resolveType(source, type));
} catch (Exception ex) {
String message = String.format("Could not read JSON:%s ", ex.getMessage());
throw new SerializationException(message, ex);
}
}
/**
* Builder method used to configure and customize the internal Jackson {@link ObjectMapper} created by this
* {@link GenericJackson2JsonRedisSerializer} and used to de/serialize {@link Object objects} as {@literal JSON}.
*
* @param objectMapperConfigurer {@link Consumer} used to configure and customize the internal {@link ObjectMapper};
* must not be {@literal null}.
* @return this {@link GenericJackson2JsonRedisSerializer}.
* @throws IllegalArgumentException if the {@link Consumer} used to configure and customize the internal
* {@link ObjectMapper} is {@literal null}.
* @since 3.1.5
*/
public GenericJackson2JsonRedisSerializer configure(Consumer<ObjectMapper> objectMapperConfigurer) {
Assert.notNull(objectMapperConfigurer, "Consumer used to configure and customize ObjectMapper must not be null");
objectMapperConfigurer.accept(getObjectMapper());
return this;
}
protected JavaType resolveType(byte[] source, Class<?> type) throws IOException {
if (!type.equals(Object.class) || !defaultTypingEnabled.get()) {
return typeResolver.constructType(type);
}
return typeResolver.resolveType(source, type);
}
/**
* @since 3.0
*/
static class TypeResolver {
// need a separate instance to bypass class hint checks
private final ObjectMapper mapper = new ObjectMapper();
private final Supplier<TypeFactory> typeFactory;
private final Supplier<String> hintName;
TypeResolver(Supplier<TypeFactory> typeFactory, Supplier<String> hintName) {
this.typeFactory = typeFactory;
this.hintName = hintName;
}
protected JavaType constructType(Class<?> type) {
return typeFactory.get().constructType(type);
}
protected JavaType resolveType(byte[] source, Class<?> type) throws IOException {
JsonNode root = mapper.readTree(source);
JsonNode jsonNode = root.get(hintName.get());
if (jsonNode instanceof TextNode && jsonNode.asText() != null) {
return typeFactory.get().constructFromCanonical(jsonNode.asText());
}
return constructType(type);
}
}
/**
* {@link StdSerializer} adding class information required by default typing. This allows de-/serialization of
* {@link NullValue}.
*
* @author Christoph Strobl
* @since 1.8
*/
private static class NullValueSerializer extends StdSerializer<NullValue> {
@Serial private static final long serialVersionUID = 1999052150548658808L;
private final String classIdentifier;
/**
* @param classIdentifier can be {@literal null} and will be defaulted to {@code @class}.
*/
NullValueSerializer(@Nullable String classIdentifier) {
super(NullValue.class);
this.classIdentifier = StringUtils.hasText(classIdentifier) ? classIdentifier : "@class";
}
@Override
public void serialize(NullValue value, JsonGenerator jsonGenerator, SerializerProvider provider)
throws IOException {
jsonGenerator.writeStartObject();
jsonGenerator.writeStringField(classIdentifier, NullValue.class.getName());
jsonGenerator.writeEndObject();
}
@Override
public void serializeWithType(NullValue value, JsonGenerator jsonGenerator, SerializerProvider serializers,
TypeSerializer typeSerializer) throws IOException {
serialize(value, jsonGenerator, serializers);
}
}
/**
* Builder for configuring and creating a {@link GenericJackson2JsonRedisSerializer}.
*
* @author Anne Lee
* @author Mark Paluch
* @since 3.3.1
*/
public static class GenericJackson2JsonRedisSerializerBuilder {
private @Nullable String typeHintPropertyName;
private JacksonObjectReader reader = JacksonObjectReader.create();
private JacksonObjectWriter writer = JacksonObjectWriter.create();
private @Nullable ObjectMapper objectMapper;
private @Nullable Boolean defaultTyping;
private boolean registerNullValueSerializer = true;
private @Nullable StdSerializer<NullValue> nullValueSerializer;
private GenericJackson2JsonRedisSerializerBuilder() {}
/**
* Enable or disable default typing. Enabling default typing will override
* {@link ObjectMapper#setDefaultTyping(com.fasterxml.jackson.databind.jsontype.TypeResolverBuilder)} for a given
* {@link ObjectMapper}. Default typing is enabled by default if no {@link ObjectMapper} is provided.
*
* @param defaultTyping whether to enable/disable default typing. Enabled by default if the {@link ObjectMapper} is
* not provided.
* @return this {@link GenericJackson2JsonRedisSerializer.GenericJackson2JsonRedisSerializerBuilder}.
*/
public GenericJackson2JsonRedisSerializerBuilder defaultTyping(boolean defaultTyping) {
this.defaultTyping = defaultTyping;
return this;
}
/**
* Configure a property name to that represents the type hint.
*
* @param typeHintPropertyName {@link String name} of the JSON property holding type information.
* @return this {@link GenericJackson2JsonRedisSerializer.GenericJackson2JsonRedisSerializerBuilder}.
*/
public GenericJackson2JsonRedisSerializerBuilder typeHintPropertyName(String typeHintPropertyName) {
Assert.hasText(typeHintPropertyName, "Type hint property name must bot be null or empty");
this.typeHintPropertyName = typeHintPropertyName;
return this;
}
/**
* Configure a provided {@link ObjectMapper}. Note that the provided {@link ObjectMapper} can be reconfigured with a
* {@link #nullValueSerializer} or default typing depending on the builder configuration.
*
* @param objectMapper must not be {@literal null}.
* @return this {@link GenericJackson2JsonRedisSerializer.GenericJackson2JsonRedisSerializerBuilder}.
*/
public GenericJackson2JsonRedisSerializerBuilder objectMapper(ObjectMapper objectMapper) {
Assert.notNull(objectMapper, "ObjectMapper must not be null");
this.objectMapper = objectMapper;
return this;
}
/**
* Configure {@link JacksonObjectReader}.
*
* @param reader must not be {@literal null}.
* @return this {@link GenericJackson2JsonRedisSerializer.GenericJackson2JsonRedisSerializerBuilder}.
*/
public GenericJackson2JsonRedisSerializerBuilder reader(JacksonObjectReader reader) {
Assert.notNull(reader, "JacksonObjectReader must not be null");
this.reader = reader;
return this;
}
/**
* Configure {@link JacksonObjectWriter}.
*
* @param writer must not be {@literal null}.
* @return this {@link GenericJackson2JsonRedisSerializer.GenericJackson2JsonRedisSerializerBuilder}.
*/
public GenericJackson2JsonRedisSerializerBuilder writer(JacksonObjectWriter writer) {
Assert.notNull(writer, "JacksonObjectWriter must not be null");
this.writer = writer;
return this;
}
/**
* Register a {@link StdSerializer serializer} for {@link NullValue}.
*
* @param nullValueSerializer the {@link StdSerializer} to use for {@link NullValue} serialization, must not be
* {@literal null}.
* @return this {@link GenericJackson2JsonRedisSerializer.GenericJackson2JsonRedisSerializerBuilder}.
*/
public GenericJackson2JsonRedisSerializerBuilder nullValueSerializer(StdSerializer<NullValue> nullValueSerializer) {
Assert.notNull(nullValueSerializer, "Null value serializer must not be null");
this.nullValueSerializer = nullValueSerializer;
return this;
}
/**
* Configure whether to register a {@link StdSerializer serializer} for {@link NullValue} serialization. The default
* serializer considers {@link #typeHintPropertyName(String)}.
*
* @param registerNullValueSerializer {@code true} to register the default serializer; {@code false} otherwise.
* @return this {@link GenericJackson2JsonRedisSerializer.GenericJackson2JsonRedisSerializerBuilder}.
*/
public GenericJackson2JsonRedisSerializerBuilder registerNullValueSerializer(boolean registerNullValueSerializer) {
this.registerNullValueSerializer = registerNullValueSerializer;
return this;
}
/**
* Creates a new instance of {@link GenericJackson2JsonRedisSerializer} with configuration options applied. Creates
* also a new {@link ObjectMapper} if none was provided.
*
* @return a new instance of {@link GenericJackson2JsonRedisSerializer}.
*/
public GenericJackson2JsonRedisSerializer build() {
ObjectMapper objectMapper = this.objectMapper;
boolean providedObjectMapper = objectMapper != null;
if (objectMapper == null) {
objectMapper = new ObjectMapper();
}
if (registerNullValueSerializer) {
objectMapper.registerModule(new SimpleModule("GenericJackson2JsonRedisSerializerBuilder")
.addSerializer(this.nullValueSerializer != null ? this.nullValueSerializer
: new NullValueSerializer(this.typeHintPropertyName)));
}
if ((!providedObjectMapper && (defaultTyping == null || defaultTyping))
|| (defaultTyping != null && defaultTyping)) {
objectMapper.setDefaultTyping(createDefaultTypeResolverBuilder(objectMapper, typeHintPropertyName));
}
return new GenericJackson2JsonRedisSerializer(objectMapper, this.reader, this.writer, this.typeHintPropertyName);
}
}
/**
* Custom {@link StdTypeResolverBuilder} that considers typing for non-primitive types. Primitives, their wrappers and
* primitive arrays do not require type hints. The default {@code DefaultTyping#EVERYTHING} typing does not satisfy
* those requirements.
*
* @author Mark Paluch
* @since 2.7.2
*/
private static class TypeResolverBuilder extends ObjectMapper.DefaultTypeResolverBuilder {
static TypeResolverBuilder forEverything(ObjectMapper mapper) {
return new TypeResolverBuilder(DefaultTyping.EVERYTHING, mapper.getPolymorphicTypeValidator());
}
public TypeResolverBuilder(DefaultTyping typing, PolymorphicTypeValidator polymorphicTypeValidator) {
super(typing, polymorphicTypeValidator);
}
@Override
public ObjectMapper.DefaultTypeResolverBuilder withDefaultImpl(Class<?> defaultImpl) {
return this;
}
/**
* Method called to check if the default type handler should be used for given type. Note: "natural types" (String,
* Boolean, Integer, Double) will never use typing; that is both due to them being concrete and final, and since
* actual serializers and deserializers will also ignore any attempts to enforce typing.
*/
public boolean useForType(JavaType javaType) {
if (javaType.isJavaLangObject()) {
return true;
}
javaType = resolveArrayOrWrapper(javaType);
if (javaType.isEnumType() || ClassUtils.isPrimitiveOrWrapper(javaType.getRawClass())) {
return false;
}
if (javaType.isFinal() && !KotlinDetector.isKotlinType(javaType.getRawClass())
&& javaType.getRawClass().getPackageName().startsWith("java")) {
return false;
}
// [databind#88] Should not apply to JSON tree models:
return !TreeNode.class.isAssignableFrom(javaType.getRawClass());
}
private JavaType resolveArrayOrWrapper(JavaType type) {
while (type.isArrayType()) {
type = type.getContentType();
if (type.isReferenceType()) {
type = resolveArrayOrWrapper(type);
}
}
while (type.isReferenceType()) {
type = type.getReferencedType();
if (type.isArrayType()) {
type = resolveArrayOrWrapper(type);
}
}
return type;
}
}
}