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