001/* 002 * Copyright 2002-2019 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.http.converter.json; 018 019import java.text.DateFormat; 020import java.text.SimpleDateFormat; 021import java.util.Arrays; 022import java.util.HashMap; 023import java.util.LinkedHashMap; 024import java.util.LinkedList; 025import java.util.List; 026import java.util.Locale; 027import java.util.Map; 028import java.util.TimeZone; 029import javax.xml.stream.XMLInputFactory; 030import javax.xml.stream.XMLResolver; 031 032import com.fasterxml.jackson.annotation.JsonFilter; 033import com.fasterxml.jackson.annotation.JsonInclude; 034import com.fasterxml.jackson.core.JsonGenerator; 035import com.fasterxml.jackson.core.JsonParser; 036import com.fasterxml.jackson.databind.AnnotationIntrospector; 037import com.fasterxml.jackson.databind.DeserializationFeature; 038import com.fasterxml.jackson.databind.JsonDeserializer; 039import com.fasterxml.jackson.databind.JsonSerializer; 040import com.fasterxml.jackson.databind.KeyDeserializer; 041import com.fasterxml.jackson.databind.MapperFeature; 042import com.fasterxml.jackson.databind.Module; 043import com.fasterxml.jackson.databind.ObjectMapper; 044import com.fasterxml.jackson.databind.PropertyNamingStrategy; 045import com.fasterxml.jackson.databind.SerializationFeature; 046import com.fasterxml.jackson.databind.cfg.HandlerInstantiator; 047import com.fasterxml.jackson.databind.jsontype.TypeResolverBuilder; 048import com.fasterxml.jackson.databind.module.SimpleModule; 049import com.fasterxml.jackson.databind.ser.FilterProvider; 050import com.fasterxml.jackson.dataformat.xml.JacksonXmlModule; 051import com.fasterxml.jackson.dataformat.xml.XmlFactory; 052import com.fasterxml.jackson.dataformat.xml.XmlMapper; 053 054import org.springframework.beans.BeanUtils; 055import org.springframework.beans.FatalBeanException; 056import org.springframework.context.ApplicationContext; 057import org.springframework.util.Assert; 058import org.springframework.util.ClassUtils; 059import org.springframework.util.LinkedMultiValueMap; 060import org.springframework.util.MultiValueMap; 061import org.springframework.util.StreamUtils; 062import org.springframework.util.StringUtils; 063 064/** 065 * A builder used to create {@link ObjectMapper} instances with a fluent API. 066 * 067 * <p>It customizes Jackson's default properties with the following ones: 068 * <ul> 069 * <li>{@link MapperFeature#DEFAULT_VIEW_INCLUSION} is disabled</li> 070 * <li>{@link DeserializationFeature#FAIL_ON_UNKNOWN_PROPERTIES} is disabled</li> 071 * </ul> 072 * 073 * <p>It also automatically registers the following well-known modules if they are 074 * detected on the classpath: 075 * <ul> 076 * <li><a href="https://github.com/FasterXML/jackson-datatype-jdk7">jackson-datatype-jdk7</a>: support for Java 7 types like {@link java.nio.file.Path}</li> 077 * <li><a href="https://github.com/FasterXML/jackson-datatype-jdk8">jackson-datatype-jdk8</a>: support for other Java 8 types like {@link java.util.Optional}</li> 078 * <li><a href="https://github.com/FasterXML/jackson-datatype-jsr310">jackson-datatype-jsr310</a>: support for Java 8 Date & Time API types</li> 079 * <li><a href="https://github.com/FasterXML/jackson-datatype-joda">jackson-datatype-joda</a>: support for Joda-Time types</li> 080 * <li><a href="https://github.com/FasterXML/jackson-module-kotlin">jackson-module-kotlin</a>: support for Kotlin classes and data classes</li> 081 * </ul> 082 * 083 * <p>Compatible with Jackson 2.6 and higher, as of Spring 4.3. 084 * 085 * @author Sebastien Deleuze 086 * @author Juergen Hoeller 087 * @author Tadaya Tsuyukubo 088 * @since 4.1.1 089 * @see #build() 090 * @see #configure(ObjectMapper) 091 * @see Jackson2ObjectMapperFactoryBean 092 */ 093public class Jackson2ObjectMapperBuilder { 094 095 private boolean createXmlMapper = false; 096 097 private DateFormat dateFormat; 098 099 private Locale locale; 100 101 private TimeZone timeZone; 102 103 private AnnotationIntrospector annotationIntrospector; 104 105 private PropertyNamingStrategy propertyNamingStrategy; 106 107 private TypeResolverBuilder<?> defaultTyping; 108 109 private JsonInclude.Include serializationInclusion; 110 111 private FilterProvider filters; 112 113 private final Map<Class<?>, Class<?>> mixIns = new HashMap<Class<?>, Class<?>>(); 114 115 private final Map<Class<?>, JsonSerializer<?>> serializers = new LinkedHashMap<Class<?>, JsonSerializer<?>>(); 116 117 private final Map<Class<?>, JsonDeserializer<?>> deserializers = new LinkedHashMap<Class<?>, JsonDeserializer<?>>(); 118 119 private final Map<Object, Boolean> features = new HashMap<Object, Boolean>(); 120 121 private List<Module> modules; 122 123 private Class<? extends Module>[] moduleClasses; 124 125 private boolean findModulesViaServiceLoader = false; 126 127 private boolean findWellKnownModules = true; 128 129 private ClassLoader moduleClassLoader = getClass().getClassLoader(); 130 131 private HandlerInstantiator handlerInstantiator; 132 133 private ApplicationContext applicationContext; 134 135 private Boolean defaultUseWrapper; 136 137 138 /** 139 * If set to {@code true}, an {@link XmlMapper} will be created using its 140 * default constructor. This is only applicable to {@link #build()} calls, 141 * not to {@link #configure} calls. 142 */ 143 public Jackson2ObjectMapperBuilder createXmlMapper(boolean createXmlMapper) { 144 this.createXmlMapper = createXmlMapper; 145 return this; 146 } 147 148 /** 149 * Define the format for date/time with the given {@link DateFormat}. 150 * <p>Note: Setting this property makes the exposed {@link ObjectMapper} 151 * non-thread-safe, according to Jackson's thread safety rules. 152 * @see #simpleDateFormat(String) 153 */ 154 public Jackson2ObjectMapperBuilder dateFormat(DateFormat dateFormat) { 155 this.dateFormat = dateFormat; 156 return this; 157 } 158 159 /** 160 * Define the date/time format with a {@link SimpleDateFormat}. 161 * <p>Note: Setting this property makes the exposed {@link ObjectMapper} 162 * non-thread-safe, according to Jackson's thread safety rules. 163 * @see #dateFormat(DateFormat) 164 */ 165 public Jackson2ObjectMapperBuilder simpleDateFormat(String format) { 166 this.dateFormat = new SimpleDateFormat(format); 167 return this; 168 } 169 170 /** 171 * Override the default {@link Locale} to use for formatting. 172 * Default value used is {@link Locale#getDefault()}. 173 * @since 4.1.5 174 */ 175 public Jackson2ObjectMapperBuilder locale(Locale locale) { 176 this.locale = locale; 177 return this; 178 } 179 180 /** 181 * Override the default {@link Locale} to use for formatting. 182 * Default value used is {@link Locale#getDefault()}. 183 * @param localeString the locale ID as a String representation 184 * @since 4.1.5 185 */ 186 public Jackson2ObjectMapperBuilder locale(String localeString) { 187 this.locale = StringUtils.parseLocaleString(localeString); 188 return this; 189 } 190 191 /** 192 * Override the default {@link TimeZone} to use for formatting. 193 * Default value used is UTC (NOT local timezone). 194 * @since 4.1.5 195 */ 196 public Jackson2ObjectMapperBuilder timeZone(TimeZone timeZone) { 197 this.timeZone = timeZone; 198 return this; 199 } 200 201 /** 202 * Override the default {@link TimeZone} to use for formatting. 203 * Default value used is UTC (NOT local timezone). 204 * @param timeZoneString the zone ID as a String representation 205 * @since 4.1.5 206 */ 207 public Jackson2ObjectMapperBuilder timeZone(String timeZoneString) { 208 this.timeZone = StringUtils.parseTimeZoneString(timeZoneString); 209 return this; 210 } 211 212 /** 213 * Set an {@link AnnotationIntrospector} for both serialization and deserialization. 214 */ 215 public Jackson2ObjectMapperBuilder annotationIntrospector(AnnotationIntrospector annotationIntrospector) { 216 this.annotationIntrospector = annotationIntrospector; 217 return this; 218 } 219 220 /** 221 * Specify a {@link com.fasterxml.jackson.databind.PropertyNamingStrategy} to 222 * configure the {@link ObjectMapper} with. 223 */ 224 public Jackson2ObjectMapperBuilder propertyNamingStrategy(PropertyNamingStrategy propertyNamingStrategy) { 225 this.propertyNamingStrategy = propertyNamingStrategy; 226 return this; 227 } 228 229 /** 230 * Specify a {@link TypeResolverBuilder} to use for Jackson's default typing. 231 * @since 4.2.2 232 */ 233 public Jackson2ObjectMapperBuilder defaultTyping(TypeResolverBuilder<?> typeResolverBuilder) { 234 this.defaultTyping = typeResolverBuilder; 235 return this; 236 } 237 238 /** 239 * Set a custom inclusion strategy for serialization. 240 * @see com.fasterxml.jackson.annotation.JsonInclude.Include 241 */ 242 public Jackson2ObjectMapperBuilder serializationInclusion(JsonInclude.Include serializationInclusion) { 243 this.serializationInclusion = serializationInclusion; 244 return this; 245 } 246 247 /** 248 * Set the global filters to use in order to support {@link JsonFilter @JsonFilter} annotated POJO. 249 * @since 4.2 250 * @see MappingJacksonValue#setFilters(FilterProvider) 251 */ 252 public Jackson2ObjectMapperBuilder filters(FilterProvider filters) { 253 this.filters = filters; 254 return this; 255 } 256 257 /** 258 * Add mix-in annotations to use for augmenting specified class or interface. 259 * @param target class (or interface) whose annotations to effectively override 260 * @param mixinSource class (or interface) whose annotations are to be "added" 261 * to target's annotations as value 262 * @since 4.1.2 263 * @see com.fasterxml.jackson.databind.ObjectMapper#addMixIn(Class, Class) 264 */ 265 public Jackson2ObjectMapperBuilder mixIn(Class<?> target, Class<?> mixinSource) { 266 if (mixinSource != null) { 267 this.mixIns.put(target, mixinSource); 268 } 269 return this; 270 } 271 272 /** 273 * Add mix-in annotations to use for augmenting specified class or interface. 274 * @param mixIns a Map of entries with target classes (or interface) whose annotations 275 * to effectively override as key and mix-in classes (or interface) whose 276 * annotations are to be "added" to target's annotations as value. 277 * @since 4.1.2 278 * @see com.fasterxml.jackson.databind.ObjectMapper#addMixIn(Class, Class) 279 */ 280 public Jackson2ObjectMapperBuilder mixIns(Map<Class<?>, Class<?>> mixIns) { 281 if (mixIns != null) { 282 this.mixIns.putAll(mixIns); 283 } 284 return this; 285 } 286 287 /** 288 * Configure custom serializers. Each serializer is registered for the type 289 * returned by {@link JsonSerializer#handledType()}, which must not be {@code null}. 290 * @see #serializersByType(Map) 291 */ 292 public Jackson2ObjectMapperBuilder serializers(JsonSerializer<?>... serializers) { 293 if (serializers != null) { 294 for (JsonSerializer<?> serializer : serializers) { 295 Class<?> handledType = serializer.handledType(); 296 if (handledType == null || handledType == Object.class) { 297 throw new IllegalArgumentException("Unknown handled type in " + serializer.getClass().getName()); 298 } 299 this.serializers.put(serializer.handledType(), serializer); 300 } 301 } 302 return this; 303 } 304 305 /** 306 * Configure a custom serializer for the given type. 307 * @since 4.1.2 308 * @see #serializers(JsonSerializer...) 309 */ 310 public Jackson2ObjectMapperBuilder serializerByType(Class<?> type, JsonSerializer<?> serializer) { 311 if (serializer != null) { 312 this.serializers.put(type, serializer); 313 } 314 return this; 315 } 316 317 /** 318 * Configure custom serializers for the given types. 319 * @see #serializers(JsonSerializer...) 320 */ 321 public Jackson2ObjectMapperBuilder serializersByType(Map<Class<?>, JsonSerializer<?>> serializers) { 322 if (serializers != null) { 323 this.serializers.putAll(serializers); 324 } 325 return this; 326 } 327 328 /** 329 * Configure custom deserializers. Each deserializer is registered for the type 330 * returned by {@link JsonDeserializer#handledType()}, which must not be {@code null}. 331 * @since 4.3 332 * @see #deserializersByType(Map) 333 */ 334 public Jackson2ObjectMapperBuilder deserializers(JsonDeserializer<?>... deserializers) { 335 if (deserializers != null) { 336 for (JsonDeserializer<?> deserializer : deserializers) { 337 Class<?> handledType = deserializer.handledType(); 338 if (handledType == null || handledType == Object.class) { 339 throw new IllegalArgumentException("Unknown handled type in " + deserializer.getClass().getName()); 340 } 341 this.deserializers.put(deserializer.handledType(), deserializer); 342 } 343 } 344 return this; 345 } 346 347 /** 348 * Configure a custom deserializer for the given type. 349 * @since 4.1.2 350 */ 351 public Jackson2ObjectMapperBuilder deserializerByType(Class<?> type, JsonDeserializer<?> deserializer) { 352 if (deserializer != null) { 353 this.deserializers.put(type, deserializer); 354 } 355 return this; 356 } 357 358 /** 359 * Configure custom deserializers for the given types. 360 */ 361 public Jackson2ObjectMapperBuilder deserializersByType(Map<Class<?>, JsonDeserializer<?>> deserializers) { 362 if (deserializers != null) { 363 this.deserializers.putAll(deserializers); 364 } 365 return this; 366 } 367 368 /** 369 * Shortcut for {@link MapperFeature#AUTO_DETECT_FIELDS} option. 370 */ 371 public Jackson2ObjectMapperBuilder autoDetectFields(boolean autoDetectFields) { 372 this.features.put(MapperFeature.AUTO_DETECT_FIELDS, autoDetectFields); 373 return this; 374 } 375 376 /** 377 * Shortcut for {@link MapperFeature#AUTO_DETECT_SETTERS}/ 378 * {@link MapperFeature#AUTO_DETECT_GETTERS}/{@link MapperFeature#AUTO_DETECT_IS_GETTERS} 379 * options. 380 */ 381 public Jackson2ObjectMapperBuilder autoDetectGettersSetters(boolean autoDetectGettersSetters) { 382 this.features.put(MapperFeature.AUTO_DETECT_GETTERS, autoDetectGettersSetters); 383 this.features.put(MapperFeature.AUTO_DETECT_SETTERS, autoDetectGettersSetters); 384 this.features.put(MapperFeature.AUTO_DETECT_IS_GETTERS, autoDetectGettersSetters); 385 return this; 386 } 387 388 /** 389 * Shortcut for {@link MapperFeature#DEFAULT_VIEW_INCLUSION} option. 390 */ 391 public Jackson2ObjectMapperBuilder defaultViewInclusion(boolean defaultViewInclusion) { 392 this.features.put(MapperFeature.DEFAULT_VIEW_INCLUSION, defaultViewInclusion); 393 return this; 394 } 395 396 /** 397 * Shortcut for {@link DeserializationFeature#FAIL_ON_UNKNOWN_PROPERTIES} option. 398 */ 399 public Jackson2ObjectMapperBuilder failOnUnknownProperties(boolean failOnUnknownProperties) { 400 this.features.put(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, failOnUnknownProperties); 401 return this; 402 } 403 404 /** 405 * Shortcut for {@link SerializationFeature#FAIL_ON_EMPTY_BEANS} option. 406 */ 407 public Jackson2ObjectMapperBuilder failOnEmptyBeans(boolean failOnEmptyBeans) { 408 this.features.put(SerializationFeature.FAIL_ON_EMPTY_BEANS, failOnEmptyBeans); 409 return this; 410 } 411 412 /** 413 * Shortcut for {@link SerializationFeature#INDENT_OUTPUT} option. 414 */ 415 public Jackson2ObjectMapperBuilder indentOutput(boolean indentOutput) { 416 this.features.put(SerializationFeature.INDENT_OUTPUT, indentOutput); 417 return this; 418 } 419 420 /** 421 * Define if a wrapper will be used for indexed (List, array) properties or not by 422 * default (only applies to {@link XmlMapper}). 423 * @since 4.3 424 */ 425 public Jackson2ObjectMapperBuilder defaultUseWrapper(boolean defaultUseWrapper) { 426 this.defaultUseWrapper = defaultUseWrapper; 427 return this; 428 } 429 430 /** 431 * Specify features to enable. 432 * @see com.fasterxml.jackson.core.JsonParser.Feature 433 * @see com.fasterxml.jackson.core.JsonGenerator.Feature 434 * @see com.fasterxml.jackson.databind.SerializationFeature 435 * @see com.fasterxml.jackson.databind.DeserializationFeature 436 * @see com.fasterxml.jackson.databind.MapperFeature 437 */ 438 public Jackson2ObjectMapperBuilder featuresToEnable(Object... featuresToEnable) { 439 if (featuresToEnable != null) { 440 for (Object feature : featuresToEnable) { 441 this.features.put(feature, Boolean.TRUE); 442 } 443 } 444 return this; 445 } 446 447 /** 448 * Specify features to disable. 449 * @see com.fasterxml.jackson.core.JsonParser.Feature 450 * @see com.fasterxml.jackson.core.JsonGenerator.Feature 451 * @see com.fasterxml.jackson.databind.SerializationFeature 452 * @see com.fasterxml.jackson.databind.DeserializationFeature 453 * @see com.fasterxml.jackson.databind.MapperFeature 454 */ 455 public Jackson2ObjectMapperBuilder featuresToDisable(Object... featuresToDisable) { 456 if (featuresToDisable != null) { 457 for (Object feature : featuresToDisable) { 458 this.features.put(feature, Boolean.FALSE); 459 } 460 } 461 return this; 462 } 463 464 /** 465 * Specify one or more modules to be registered with the {@link ObjectMapper}. 466 * Multiple invocations are not additive, the last one defines the modules to 467 * register. 468 * <p>Note: If this is set, no finding of modules is going to happen - not by 469 * Jackson, and not by Spring either (see {@link #findModulesViaServiceLoader}). 470 * As a consequence, specifying an empty list here will suppress any kind of 471 * module detection. 472 * <p>Specify either this or {@link #modulesToInstall}, not both. 473 * @since 4.1.5 474 * @see #modules(List) 475 * @see com.fasterxml.jackson.databind.Module 476 */ 477 public Jackson2ObjectMapperBuilder modules(Module... modules) { 478 return modules(Arrays.asList(modules)); 479 } 480 481 /** 482 * Set a complete list of modules to be registered with the {@link ObjectMapper}. 483 * Multiple invocations are not additive, the last one defines the modules to 484 * register. 485 * <p>Note: If this is set, no finding of modules is going to happen - not by 486 * Jackson, and not by Spring either (see {@link #findModulesViaServiceLoader}). 487 * As a consequence, specifying an empty list here will suppress any kind of 488 * module detection. 489 * <p>Specify either this or {@link #modulesToInstall}, not both. 490 * @see #modules(Module...) 491 * @see com.fasterxml.jackson.databind.Module 492 */ 493 public Jackson2ObjectMapperBuilder modules(List<Module> modules) { 494 this.modules = new LinkedList<Module>(modules); 495 this.findModulesViaServiceLoader = false; 496 this.findWellKnownModules = false; 497 return this; 498 } 499 500 /** 501 * Specify one or more modules to be registered with the {@link ObjectMapper}. 502 * Multiple invocations are not additive, the last one defines the modules 503 * to register. 504 * <p>Modules specified here will be registered after 505 * Spring's autodetection of JSR-310 and Joda-Time, or Jackson's 506 * finding of modules (see {@link #findModulesViaServiceLoader}), 507 * allowing to eventually override their configuration. 508 * <p>Specify either this or {@link #modules}, not both. 509 * @since 4.1.5 510 * @see com.fasterxml.jackson.databind.Module 511 */ 512 public Jackson2ObjectMapperBuilder modulesToInstall(Module... modules) { 513 this.modules = Arrays.asList(modules); 514 this.findWellKnownModules = true; 515 return this; 516 } 517 518 /** 519 * Specify one or more modules by class to be registered with 520 * the {@link ObjectMapper}. Multiple invocations are not additive, 521 * the last one defines the modules to register. 522 * <p>Modules specified here will be registered after 523 * Spring's autodetection of JSR-310 and Joda-Time, or Jackson's 524 * finding of modules (see {@link #findModulesViaServiceLoader}), 525 * allowing to eventually override their configuration. 526 * <p>Specify either this or {@link #modules}, not both. 527 * @see #modulesToInstall(Module...) 528 * @see com.fasterxml.jackson.databind.Module 529 */ 530 @SuppressWarnings("unchecked") 531 public Jackson2ObjectMapperBuilder modulesToInstall(Class<? extends Module>... modules) { 532 this.moduleClasses = modules; 533 this.findWellKnownModules = true; 534 return this; 535 } 536 537 /** 538 * Set whether to let Jackson find available modules via the JDK ServiceLoader, 539 * based on META-INF metadata in the classpath. 540 * <p>If this mode is not set, Spring's Jackson2ObjectMapperBuilder itself 541 * will try to find the JSR-310 and Joda-Time support modules on the classpath - 542 * provided that Java 8 and Joda-Time themselves are available, respectively. 543 * @see com.fasterxml.jackson.databind.ObjectMapper#findModules() 544 */ 545 public Jackson2ObjectMapperBuilder findModulesViaServiceLoader(boolean findModules) { 546 this.findModulesViaServiceLoader = findModules; 547 return this; 548 } 549 550 /** 551 * Set the ClassLoader to use for loading Jackson extension modules. 552 */ 553 public Jackson2ObjectMapperBuilder moduleClassLoader(ClassLoader moduleClassLoader) { 554 this.moduleClassLoader = moduleClassLoader; 555 return this; 556 } 557 558 /** 559 * Customize the construction of Jackson handlers ({@link JsonSerializer}, {@link JsonDeserializer}, 560 * {@link KeyDeserializer}, {@code TypeResolverBuilder} and {@code TypeIdResolver}). 561 * @since 4.1.3 562 * @see Jackson2ObjectMapperBuilder#applicationContext(ApplicationContext) 563 */ 564 public Jackson2ObjectMapperBuilder handlerInstantiator(HandlerInstantiator handlerInstantiator) { 565 this.handlerInstantiator = handlerInstantiator; 566 return this; 567 } 568 569 /** 570 * Set the Spring {@link ApplicationContext} in order to autowire Jackson handlers ({@link JsonSerializer}, 571 * {@link JsonDeserializer}, {@link KeyDeserializer}, {@code TypeResolverBuilder} and {@code TypeIdResolver}). 572 * @since 4.1.3 573 * @see SpringHandlerInstantiator 574 */ 575 public Jackson2ObjectMapperBuilder applicationContext(ApplicationContext applicationContext) { 576 this.applicationContext = applicationContext; 577 return this; 578 } 579 580 581 /** 582 * Build a new {@link ObjectMapper} instance. 583 * <p>Each build operation produces an independent {@link ObjectMapper} instance. 584 * The builder's settings can get modified, with a subsequent build operation 585 * then producing a new {@link ObjectMapper} based on the most recent settings. 586 * @return the newly built ObjectMapper 587 */ 588 @SuppressWarnings("unchecked") 589 public <T extends ObjectMapper> T build() { 590 ObjectMapper mapper; 591 if (this.createXmlMapper) { 592 mapper = (this.defaultUseWrapper != null ? 593 new XmlObjectMapperInitializer().create(this.defaultUseWrapper) : 594 new XmlObjectMapperInitializer().create()); 595 } 596 else { 597 mapper = new ObjectMapper(); 598 } 599 configure(mapper); 600 return (T) mapper; 601 } 602 603 /** 604 * Configure an existing {@link ObjectMapper} instance with this builder's 605 * settings. This can be applied to any number of {@code ObjectMappers}. 606 * @param objectMapper the ObjectMapper to configure 607 */ 608 public void configure(ObjectMapper objectMapper) { 609 Assert.notNull(objectMapper, "ObjectMapper must not be null"); 610 611 MultiValueMap<Object, Module> modulesToRegister = new LinkedMultiValueMap<Object, Module>(); 612 if (this.findModulesViaServiceLoader) { 613 for (Module module : ObjectMapper.findModules(this.moduleClassLoader)) { 614 registerModule(module, modulesToRegister); 615 } 616 } 617 else if (this.findWellKnownModules) { 618 registerWellKnownModulesIfAvailable(modulesToRegister); 619 } 620 621 if (this.modules != null) { 622 for (Module module : this.modules) { 623 registerModule(module, modulesToRegister); 624 } 625 } 626 if (this.moduleClasses != null) { 627 for (Class<? extends Module> moduleClass : this.moduleClasses) { 628 registerModule(BeanUtils.instantiateClass(moduleClass), modulesToRegister); 629 } 630 } 631 // Using Jackson 2.0+ registerModule method, not Jackson 2.2+ registerModules 632 for (List<Module> nestedModules : modulesToRegister.values()) { 633 for (Module module : nestedModules) { 634 objectMapper.registerModule(module); 635 } 636 } 637 638 if (this.dateFormat != null) { 639 objectMapper.setDateFormat(this.dateFormat); 640 } 641 if (this.locale != null) { 642 objectMapper.setLocale(this.locale); 643 } 644 if (this.timeZone != null) { 645 objectMapper.setTimeZone(this.timeZone); 646 } 647 648 if (this.annotationIntrospector != null) { 649 objectMapper.setAnnotationIntrospector(this.annotationIntrospector); 650 } 651 if (this.propertyNamingStrategy != null) { 652 objectMapper.setPropertyNamingStrategy(this.propertyNamingStrategy); 653 } 654 if (this.defaultTyping != null) { 655 objectMapper.setDefaultTyping(this.defaultTyping); 656 } 657 if (this.serializationInclusion != null) { 658 objectMapper.setSerializationInclusion(this.serializationInclusion); 659 } 660 661 if (this.filters != null) { 662 objectMapper.setFilterProvider(this.filters); 663 } 664 665 for (Class<?> target : this.mixIns.keySet()) { 666 objectMapper.addMixIn(target, this.mixIns.get(target)); 667 } 668 669 if (!this.serializers.isEmpty() || !this.deserializers.isEmpty()) { 670 SimpleModule module = new SimpleModule(); 671 addSerializers(module); 672 addDeserializers(module); 673 objectMapper.registerModule(module); 674 } 675 676 customizeDefaultFeatures(objectMapper); 677 for (Object feature : this.features.keySet()) { 678 configureFeature(objectMapper, feature, this.features.get(feature)); 679 } 680 681 if (this.handlerInstantiator != null) { 682 objectMapper.setHandlerInstantiator(this.handlerInstantiator); 683 } 684 else if (this.applicationContext != null) { 685 objectMapper.setHandlerInstantiator( 686 new SpringHandlerInstantiator(this.applicationContext.getAutowireCapableBeanFactory())); 687 } 688 } 689 690 private void registerModule(Module module, MultiValueMap<Object, Module> modulesToRegister) { 691 if (module.getTypeId() == null) { 692 modulesToRegister.add(SimpleModule.class.getName(), module); 693 } 694 else { 695 modulesToRegister.set(module.getTypeId(), module); 696 } 697 } 698 699 700 // Any change to this method should be also applied to spring-jms and spring-messaging 701 // MappingJackson2MessageConverter default constructors 702 private void customizeDefaultFeatures(ObjectMapper objectMapper) { 703 if (!this.features.containsKey(MapperFeature.DEFAULT_VIEW_INCLUSION)) { 704 configureFeature(objectMapper, MapperFeature.DEFAULT_VIEW_INCLUSION, false); 705 } 706 if (!this.features.containsKey(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)) { 707 configureFeature(objectMapper, DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); 708 } 709 } 710 711 @SuppressWarnings("unchecked") 712 private <T> void addSerializers(SimpleModule module) { 713 for (Class<?> type : this.serializers.keySet()) { 714 module.addSerializer((Class<? extends T>) type, (JsonSerializer<T>) this.serializers.get(type)); 715 } 716 } 717 718 @SuppressWarnings("unchecked") 719 private <T> void addDeserializers(SimpleModule module) { 720 for (Class<?> type : this.deserializers.keySet()) { 721 module.addDeserializer((Class<T>) type, (JsonDeserializer<? extends T>) this.deserializers.get(type)); 722 } 723 } 724 725 private void configureFeature(ObjectMapper objectMapper, Object feature, boolean enabled) { 726 if (feature instanceof JsonParser.Feature) { 727 objectMapper.configure((JsonParser.Feature) feature, enabled); 728 } 729 else if (feature instanceof JsonGenerator.Feature) { 730 objectMapper.configure((JsonGenerator.Feature) feature, enabled); 731 } 732 else if (feature instanceof SerializationFeature) { 733 objectMapper.configure((SerializationFeature) feature, enabled); 734 } 735 else if (feature instanceof DeserializationFeature) { 736 objectMapper.configure((DeserializationFeature) feature, enabled); 737 } 738 else if (feature instanceof MapperFeature) { 739 objectMapper.configure((MapperFeature) feature, enabled); 740 } 741 else { 742 throw new FatalBeanException("Unknown feature class: " + feature.getClass().getName()); 743 } 744 } 745 746 @SuppressWarnings("unchecked") 747 private void registerWellKnownModulesIfAvailable(MultiValueMap<Object, Module> modulesToRegister) { 748 // Java 7 java.nio.file.Path class present? 749 if (ClassUtils.isPresent("java.nio.file.Path", this.moduleClassLoader)) { 750 try { 751 Class<? extends Module> jdk7ModuleClass = (Class<? extends Module>) 752 ClassUtils.forName("com.fasterxml.jackson.datatype.jdk7.Jdk7Module", this.moduleClassLoader); 753 Module jdk7Module = BeanUtils.instantiateClass(jdk7ModuleClass); 754 modulesToRegister.set(jdk7Module.getTypeId(), jdk7Module); 755 } 756 catch (ClassNotFoundException ex) { 757 // jackson-datatype-jdk7 not available 758 } 759 } 760 761 // Java 8 java.util.Optional class present? 762 if (ClassUtils.isPresent("java.util.Optional", this.moduleClassLoader)) { 763 try { 764 Class<? extends Module> jdk8ModuleClass = (Class<? extends Module>) 765 ClassUtils.forName("com.fasterxml.jackson.datatype.jdk8.Jdk8Module", this.moduleClassLoader); 766 Module jdk8Module = BeanUtils.instantiateClass(jdk8ModuleClass); 767 modulesToRegister.set(jdk8Module.getTypeId(), jdk8Module); 768 } 769 catch (ClassNotFoundException ex) { 770 // jackson-datatype-jdk8 not available 771 } 772 } 773 774 // Java 8 java.time package present? 775 if (ClassUtils.isPresent("java.time.LocalDate", this.moduleClassLoader)) { 776 try { 777 Class<? extends Module> javaTimeModuleClass = (Class<? extends Module>) 778 ClassUtils.forName("com.fasterxml.jackson.datatype.jsr310.JavaTimeModule", this.moduleClassLoader); 779 Module javaTimeModule = BeanUtils.instantiateClass(javaTimeModuleClass); 780 modulesToRegister.set(javaTimeModule.getTypeId(), javaTimeModule); 781 } 782 catch (ClassNotFoundException ex) { 783 // jackson-datatype-jsr310 not available 784 } 785 } 786 787 // Joda-Time present? 788 if (ClassUtils.isPresent("org.joda.time.LocalDate", this.moduleClassLoader)) { 789 try { 790 Class<? extends Module> jodaModuleClass = (Class<? extends Module>) 791 ClassUtils.forName("com.fasterxml.jackson.datatype.joda.JodaModule", this.moduleClassLoader); 792 Module jodaModule = BeanUtils.instantiateClass(jodaModuleClass); 793 modulesToRegister.set(jodaModule.getTypeId(), jodaModule); 794 } 795 catch (ClassNotFoundException ex) { 796 // jackson-datatype-joda not available 797 } 798 } 799 800 // Kotlin present? 801 if (ClassUtils.isPresent("kotlin.Unit", this.moduleClassLoader)) { 802 try { 803 Class<? extends Module> kotlinModuleClass = (Class<? extends Module>) 804 ClassUtils.forName("com.fasterxml.jackson.module.kotlin.KotlinModule", this.moduleClassLoader); 805 Module kotlinModule = BeanUtils.instantiateClass(kotlinModuleClass); 806 modulesToRegister.set(kotlinModule.getTypeId(), kotlinModule); 807 } 808 catch (ClassNotFoundException ex) { 809 // jackson-module-kotlin not available 810 } 811 } 812 } 813 814 815 // Convenience factory methods 816 817 /** 818 * Obtain a {@link Jackson2ObjectMapperBuilder} instance in order to 819 * build a regular JSON {@link ObjectMapper} instance. 820 */ 821 public static Jackson2ObjectMapperBuilder json() { 822 return new Jackson2ObjectMapperBuilder(); 823 } 824 825 /** 826 * Obtain a {@link Jackson2ObjectMapperBuilder} instance in order to 827 * build an {@link XmlMapper} instance. 828 */ 829 public static Jackson2ObjectMapperBuilder xml() { 830 return new Jackson2ObjectMapperBuilder().createXmlMapper(true); 831 } 832 833 834 private static class XmlObjectMapperInitializer { 835 836 public ObjectMapper create() { 837 return new XmlMapper(xmlInputFactory()); 838 } 839 840 public ObjectMapper create(boolean defaultUseWrapper) { 841 JacksonXmlModule module = new JacksonXmlModule(); 842 module.setDefaultUseWrapper(defaultUseWrapper); 843 return new XmlMapper(new XmlFactory(xmlInputFactory()), module); 844 } 845 846 private static XMLInputFactory xmlInputFactory() { 847 XMLInputFactory inputFactory = XMLInputFactory.newInstance(); 848 inputFactory.setProperty(XMLInputFactory.SUPPORT_DTD, false); 849 inputFactory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false); 850 inputFactory.setXMLResolver(NO_OP_XML_RESOLVER); 851 return inputFactory; 852 } 853 854 private static final XMLResolver NO_OP_XML_RESOLVER = new XMLResolver() { 855 @Override 856 public Object resolveEntity(String publicID, String systemID, String base, String ns) { 857 return StreamUtils.emptyInput(); 858 } 859 }; 860 } 861 862}