47
47
/**
48
48
* A builder used to create {@link ObjectMapper} instances with a fluent API.
49
49
*
50
- * <p>It customizes Jackson defaults properties with the following ones:
50
+ * <p>It customizes Jackson's default properties with the following ones:
51
51
* <ul>
52
- * <li>{@link MapperFeature#DEFAULT_VIEW_INCLUSION} is disabled</li>
53
- * <li>{@link DeserializationFeature#FAIL_ON_UNKNOWN_PROPERTIES} is disabled</li>
52
+ * <li>{@link MapperFeature#DEFAULT_VIEW_INCLUSION} is disabled</li>
53
+ * <li>{@link DeserializationFeature#FAIL_ON_UNKNOWN_PROPERTIES} is disabled</li>
54
54
* </ul>
55
55
*
56
56
* <p>Note that Jackson's JSR-310 and Joda-Time support modules will be registered automatically
59
59
* <p>Tested against Jackson 2.2 and 2.3; compatible with Jackson 2.0 and higher.
60
60
*
61
61
* @author Sebastien Deleuze
62
+ * @author Juergen Hoeller
62
63
* @since 4.1.1
64
+ * @see #build()
65
+ * @see #configure(ObjectMapper)
66
+ * @see Jackson2ObjectMapperFactoryBean
63
67
*/
64
68
public class Jackson2ObjectMapperBuilder {
65
69
66
- private static final boolean jackson2XmlPresent =
67
- ClassUtils .isPresent ("com.fasterxml.jackson.dataformat.xml.XmlMapper" , getClassLoader ());
68
-
69
- private ObjectMapper objectMapper ;
70
-
71
70
private boolean createXmlMapper = false ;
72
71
73
72
private DateFormat dateFormat ;
74
73
75
- private JsonInclude .Include serializationInclusion ;
76
-
77
74
private AnnotationIntrospector annotationIntrospector ;
78
75
79
- private final Map <Class <?>, JsonSerializer <?>>
80
- serializers = new LinkedHashMap <Class <?>, JsonSerializer <?>>();
76
+ private PropertyNamingStrategy propertyNamingStrategy ;
77
+
78
+ private JsonInclude .Include serializationInclusion ;
79
+
80
+ private final Map <Class <?>, JsonSerializer <?>> serializers = new LinkedHashMap <Class <?>, JsonSerializer <?>>();
81
81
82
- private final Map <Class <?>, JsonDeserializer <?>>
83
- deserializers = new LinkedHashMap <Class <?>, JsonDeserializer <?>>();
82
+ private final Map <Class <?>, JsonDeserializer <?>> deserializers = new LinkedHashMap <Class <?>, JsonDeserializer <?>>();
84
83
85
84
private final Map <Object , Boolean > features = new HashMap <Object , Boolean >();
86
85
@@ -90,47 +89,35 @@ public class Jackson2ObjectMapperBuilder {
90
89
91
90
private boolean findModulesViaServiceLoader ;
92
91
93
- private PropertyNamingStrategy propertyNamingStrategy ;
92
+ private ClassLoader moduleClassLoader = getClass (). getClassLoader () ;
94
93
95
94
96
95
private Jackson2ObjectMapperBuilder () {
97
96
}
98
97
99
- private Jackson2ObjectMapperBuilder (ObjectMapper objectMapper ) {
100
- this .objectMapper = objectMapper ;
101
- }
102
98
103
99
/**
104
- * Obtain a {@link Jackson2ObjectMapperBuilder} instance in order to build an {@link ObjectMapper} instance.
100
+ * Obtain a {@link Jackson2ObjectMapperBuilder} instance in order to
101
+ * build an {@link ObjectMapper} instance.
105
102
*/
106
103
public static Jackson2ObjectMapperBuilder json () {
107
104
return new Jackson2ObjectMapperBuilder ();
108
105
}
109
106
110
107
/**
111
- * Obtain a {@link Jackson2ObjectMapperBuilder} instance in order to build a {@link XmlMapper} instance.
108
+ * Obtain a {@link Jackson2ObjectMapperBuilder} instance in order to
109
+ * build a {@link XmlMapper} instance.
112
110
*/
113
111
@ SuppressWarnings ("unchecked" )
114
112
public static Jackson2ObjectMapperBuilder xml () {
115
113
return new Jackson2ObjectMapperBuilder ().createXmlMapper (true );
116
114
}
117
115
118
- /**
119
- * Obtain a {@link Jackson2ObjectMapperBuilder} in order to customize the {@link ObjectMapper} parameter.
120
- */
121
- public static Jackson2ObjectMapperBuilder instance (ObjectMapper objectMapper ) {
122
- return new Jackson2ObjectMapperBuilder (objectMapper );
123
- }
124
-
125
- private static ClassLoader getClassLoader () {
126
- ClassLoader classLoader = ClassUtils .getDefaultClassLoader ();
127
- Assert .state (classLoader != null , "No classloader available" );
128
- return classLoader ;
129
- }
130
116
131
117
/**
132
- * If set to true and no custom {@link ObjectMapper} has been set, a {@link XmlMapper}
133
- * will be created using its default constructor.
118
+ * If set to {@code true}, an {@link XmlMapper} will be created using its
119
+ * default constructor. This is only applicable to {@link #build()} calls,
120
+ * not to {@link #configure} calls.
134
121
*/
135
122
public Jackson2ObjectMapperBuilder createXmlMapper (boolean createXmlMapper ) {
136
123
this .createXmlMapper = createXmlMapper ;
@@ -167,6 +154,15 @@ public Jackson2ObjectMapperBuilder annotationIntrospector(AnnotationIntrospector
167
154
return this ;
168
155
}
169
156
157
+ /**
158
+ * Specify a {@link com.fasterxml.jackson.databind.PropertyNamingStrategy} to
159
+ * configure the {@link ObjectMapper} with.
160
+ */
161
+ public Jackson2ObjectMapperBuilder propertyNamingStrategy (PropertyNamingStrategy propertyNamingStrategy ) {
162
+ this .propertyNamingStrategy = propertyNamingStrategy ;
163
+ return this ;
164
+ }
165
+
170
166
/**
171
167
* Set a custom inclusion strategy for serialization.
172
168
* @see com.fasterxml.jackson.annotation.JsonInclude.Include
@@ -186,8 +182,9 @@ public Jackson2ObjectMapperBuilder serializers(JsonSerializer<?>... serializers)
186
182
if (serializers != null ) {
187
183
for (JsonSerializer <?> serializer : serializers ) {
188
184
Class <?> handledType = serializer .handledType ();
189
- Assert .isTrue (handledType != null && handledType != Object .class ,
190
- "Unknown handled type in " + serializer .getClass ().getName ());
185
+ if (handledType == null || handledType == Object .class ) {
186
+ throw new IllegalArgumentException ("Unknown handled type in " + serializer .getClass ().getName ());
187
+ }
191
188
this .serializers .put (serializer .handledType (), serializer );
192
189
}
193
190
}
@@ -234,18 +231,18 @@ public Jackson2ObjectMapperBuilder autoDetectGettersSetters(boolean autoDetectGe
234
231
}
235
232
236
233
/**
237
- * Shortcut for {@link DeserializationFeature#FAIL_ON_UNKNOWN_PROPERTIES } option.
234
+ * Shortcut for {@link MapperFeature#DEFAULT_VIEW_INCLUSION } option.
238
235
*/
239
- public Jackson2ObjectMapperBuilder failOnUnknownProperties (boolean failOnUnknownProperties ) {
240
- this .features .put (DeserializationFeature . FAIL_ON_UNKNOWN_PROPERTIES , failOnUnknownProperties );
236
+ public Jackson2ObjectMapperBuilder defaultViewInclusion (boolean defaultViewInclusion ) {
237
+ this .features .put (MapperFeature . DEFAULT_VIEW_INCLUSION , defaultViewInclusion );
241
238
return this ;
242
239
}
243
240
244
241
/**
245
- * Shortcut for {@link MapperFeature#DEFAULT_VIEW_INCLUSION } option.
242
+ * Shortcut for {@link DeserializationFeature#FAIL_ON_UNKNOWN_PROPERTIES } option.
246
243
*/
247
- public Jackson2ObjectMapperBuilder defaultViewInclusion (boolean defaultViewInclusion ) {
248
- this .features .put (MapperFeature . DEFAULT_VIEW_INCLUSION , defaultViewInclusion );
244
+ public Jackson2ObjectMapperBuilder failOnUnknownProperties (boolean failOnUnknownProperties ) {
245
+ this .features .put (DeserializationFeature . FAIL_ON_UNKNOWN_PROPERTIES , failOnUnknownProperties );
249
246
return this ;
250
247
}
251
248
@@ -341,93 +338,104 @@ public Jackson2ObjectMapperBuilder findModulesViaServiceLoader(boolean findModul
341
338
}
342
339
343
340
/**
344
- * Specify a {@link com.fasterxml.jackson.databind.PropertyNamingStrategy} to
345
- * configure the {@link ObjectMapper} with.
341
+ * Set the ClassLoader to use for loading Jackson extension modules.
346
342
*/
347
- public Jackson2ObjectMapperBuilder propertyNamingStrategy ( PropertyNamingStrategy propertyNamingStrategy ) {
348
- this .propertyNamingStrategy = propertyNamingStrategy ;
343
+ public Jackson2ObjectMapperBuilder moduleClassLoader ( ClassLoader moduleClassLoader ) {
344
+ this .moduleClassLoader = moduleClassLoader ;
349
345
return this ;
350
346
}
351
347
348
+
352
349
/**
353
- * Build a new {@link T} instance.
350
+ * Build a new {@link ObjectMapper} instance.
351
+ * <p>Each build operation produces an independent {@link ObjectMapper} instance.
352
+ * The builder's settings can get modified, with a subsequent build operation
353
+ * then producing a new {@link ObjectMapper} based on the most recent settings.
354
+ * @return the newly built ObjectMapper
354
355
*/
355
356
@ SuppressWarnings ("unchecked" )
356
357
public <T extends ObjectMapper > T build () {
357
- if (this .objectMapper == null ) {
358
- if (this .createXmlMapper ) {
359
- ClassLoader cl = getClassLoader ();
360
- try {
361
- Class <? extends ObjectMapper > xmlMapper = (Class <? extends ObjectMapper >)
362
- cl .loadClass ("com.fasterxml.jackson.dataformat.xml.XmlMapper" );
363
- this .objectMapper = BeanUtils .instantiate (xmlMapper );
364
- }
365
- catch (ClassNotFoundException ex ) {
366
- throw new IllegalStateException ("Could not instantiate XmlMapper, it has not been found on the classpath" );
367
- }
358
+ ObjectMapper objectMapper ;
359
+ if (this .createXmlMapper ) {
360
+ try {
361
+ Class <? extends ObjectMapper > xmlMapper = (Class <? extends ObjectMapper >)
362
+ ClassUtils .forName ("com.fasterxml.jackson.dataformat.xml.XmlMapper" , this .moduleClassLoader );
363
+ objectMapper = BeanUtils .instantiate (xmlMapper );
368
364
}
369
- else {
370
- this . objectMapper = new ObjectMapper ( );
365
+ catch ( ClassNotFoundException ex ) {
366
+ throw new IllegalStateException ( "Could not instantiate XmlMapper - not found on classpath" );
371
367
}
372
368
}
369
+ else {
370
+ objectMapper = new ObjectMapper ();
371
+ }
372
+ configure (objectMapper );
373
+ return (T ) objectMapper ;
374
+ }
375
+
376
+ /**
377
+ * Configure an existing {@link ObjectMapper} instance with this builder's
378
+ * settings. This can be applied to any number of {@code ObjectMappers}.
379
+ * @param objectMapper the ObjectMapper to configure
380
+ */
381
+ public void configure (ObjectMapper objectMapper ) {
382
+ Assert .notNull (objectMapper , "ObjectMapper must not be null" );
373
383
374
384
if (this .dateFormat != null ) {
375
- this . objectMapper .setDateFormat (this .dateFormat );
385
+ objectMapper .setDateFormat (this .dateFormat );
376
386
}
377
387
378
388
if (this .annotationIntrospector != null ) {
379
- this . objectMapper .setAnnotationIntrospector (this .annotationIntrospector );
389
+ objectMapper .setAnnotationIntrospector (this .annotationIntrospector );
380
390
}
381
391
382
392
if (this .serializationInclusion != null ) {
383
- this . objectMapper .setSerializationInclusion (this .serializationInclusion );
393
+ objectMapper .setSerializationInclusion (this .serializationInclusion );
384
394
}
385
395
386
396
if (!this .serializers .isEmpty () || !this .deserializers .isEmpty ()) {
387
397
SimpleModule module = new SimpleModule ();
388
398
addSerializers (module );
389
399
addDeserializers (module );
390
- this . objectMapper .registerModule (module );
400
+ objectMapper .registerModule (module );
391
401
}
392
402
393
- if (! features .containsKey (MapperFeature .DEFAULT_VIEW_INCLUSION )) {
394
- configureFeature (MapperFeature .DEFAULT_VIEW_INCLUSION , false );
403
+ if (! this . features .containsKey (MapperFeature .DEFAULT_VIEW_INCLUSION )) {
404
+ configureFeature (objectMapper , MapperFeature .DEFAULT_VIEW_INCLUSION , false );
395
405
}
396
- if (! features .containsKey (DeserializationFeature .FAIL_ON_UNKNOWN_PROPERTIES )) {
397
- configureFeature (DeserializationFeature .FAIL_ON_UNKNOWN_PROPERTIES , false );
406
+ if (! this . features .containsKey (DeserializationFeature .FAIL_ON_UNKNOWN_PROPERTIES )) {
407
+ configureFeature (objectMapper , DeserializationFeature .FAIL_ON_UNKNOWN_PROPERTIES , false );
398
408
}
399
409
for (Object feature : this .features .keySet ()) {
400
- configureFeature (feature , this .features .get (feature ));
410
+ configureFeature (objectMapper , feature , this .features .get (feature ));
401
411
}
402
412
403
413
if (this .modules != null ) {
404
414
// Complete list of modules given
405
415
for (Module module : this .modules ) {
406
416
// Using Jackson 2.0+ registerModule method, not Jackson 2.2+ registerModules
407
- this . objectMapper .registerModule (module );
417
+ objectMapper .registerModule (module );
408
418
}
409
419
}
410
420
else {
411
421
// Combination of modules by class names specified and class presence in the classpath
412
422
if (this .modulesToInstall != null ) {
413
423
for (Class <? extends Module > module : this .modulesToInstall ) {
414
- this . objectMapper .registerModule (BeanUtils .instantiate (module ));
424
+ objectMapper .registerModule (BeanUtils .instantiate (module ));
415
425
}
416
426
}
417
427
if (this .findModulesViaServiceLoader ) {
418
428
// Jackson 2.2+
419
- this . objectMapper .registerModules (ObjectMapper .findModules (getClassLoader () ));
429
+ objectMapper .registerModules (ObjectMapper .findModules (this . moduleClassLoader ));
420
430
}
421
431
else {
422
- registerWellKnownModulesIfAvailable ();
432
+ registerWellKnownModulesIfAvailable (objectMapper );
423
433
}
424
434
}
425
435
426
436
if (this .propertyNamingStrategy != null ) {
427
- this . objectMapper .setPropertyNamingStrategy (this .propertyNamingStrategy );
437
+ objectMapper .setPropertyNamingStrategy (this .propertyNamingStrategy );
428
438
}
429
-
430
- return (T )this .objectMapper ;
431
439
}
432
440
433
441
@ SuppressWarnings ("unchecked" )
@@ -444,47 +452,46 @@ private <T> void addDeserializers(SimpleModule module) {
444
452
}
445
453
}
446
454
447
- private void configureFeature (Object feature , boolean enabled ) {
455
+ private void configureFeature (ObjectMapper objectMapper , Object feature , boolean enabled ) {
448
456
if (feature instanceof JsonParser .Feature ) {
449
- this . objectMapper .configure ((JsonParser .Feature ) feature , enabled );
457
+ objectMapper .configure ((JsonParser .Feature ) feature , enabled );
450
458
}
451
459
else if (feature instanceof JsonGenerator .Feature ) {
452
- this . objectMapper .configure ((JsonGenerator .Feature ) feature , enabled );
460
+ objectMapper .configure ((JsonGenerator .Feature ) feature , enabled );
453
461
}
454
462
else if (feature instanceof SerializationFeature ) {
455
- this . objectMapper .configure ((SerializationFeature ) feature , enabled );
463
+ objectMapper .configure ((SerializationFeature ) feature , enabled );
456
464
}
457
465
else if (feature instanceof DeserializationFeature ) {
458
- this . objectMapper .configure ((DeserializationFeature ) feature , enabled );
466
+ objectMapper .configure ((DeserializationFeature ) feature , enabled );
459
467
}
460
468
else if (feature instanceof MapperFeature ) {
461
- this . objectMapper .configure ((MapperFeature ) feature , enabled );
469
+ objectMapper .configure ((MapperFeature ) feature , enabled );
462
470
}
463
471
else {
464
472
throw new FatalBeanException ("Unknown feature class: " + feature .getClass ().getName ());
465
473
}
466
474
}
467
475
468
476
@ SuppressWarnings ("unchecked" )
469
- private void registerWellKnownModulesIfAvailable () {
470
- ClassLoader cl = getClassLoader ();
477
+ private void registerWellKnownModulesIfAvailable (ObjectMapper objectMapper ) {
471
478
// Java 8 java.time package present?
472
- if (ClassUtils .isPresent ("java.time.LocalDate" , cl )) {
479
+ if (ClassUtils .isPresent ("java.time.LocalDate" , this . moduleClassLoader )) {
473
480
try {
474
481
Class <? extends Module > jsr310Module = (Class <? extends Module >)
475
- cl . loadClass ("com.fasterxml.jackson.datatype.jsr310.JSR310Module" );
476
- this . objectMapper .registerModule (BeanUtils .instantiate (jsr310Module ));
482
+ ClassUtils . forName ("com.fasterxml.jackson.datatype.jsr310.JSR310Module" , this . moduleClassLoader );
483
+ objectMapper .registerModule (BeanUtils .instantiate (jsr310Module ));
477
484
}
478
485
catch (ClassNotFoundException ex ) {
479
486
// jackson-datatype-jsr310 not available
480
487
}
481
488
}
482
489
// Joda-Time present?
483
- if (ClassUtils .isPresent ("org.joda.time.LocalDate" , cl )) {
490
+ if (ClassUtils .isPresent ("org.joda.time.LocalDate" , this . moduleClassLoader )) {
484
491
try {
485
492
Class <? extends Module > jodaModule = (Class <? extends Module >)
486
- cl . loadClass ("com.fasterxml.jackson.datatype.joda.JodaModule" );
487
- this . objectMapper .registerModule (BeanUtils .instantiate (jodaModule ));
493
+ ClassUtils . forName ("com.fasterxml.jackson.datatype.joda.JodaModule" , this . moduleClassLoader );
494
+ objectMapper .registerModule (BeanUtils .instantiate (jodaModule ));
488
495
}
489
496
catch (ClassNotFoundException ex ) {
490
497
// jackson-datatype-joda not available
0 commit comments