Conversation
Jackson 3 ObjectMappers are immutable, so the Jackson 2 idiom Json.mapper().registerModule(...) no longer works, and the default ModelResolver copies its mapper so a replaced Json.mapper() would not reach model introspection either. - ObjectMapperFactory: MapperCustomizer registry (addCustomizer, addModule(s), remove/clear, generation counter) applied as the last step of every mapper it builds, keyed by MapperTarget. - Json/Yaml/Json31/Yaml31: mapper() is now lazily built and rebuilt when customizers change; new mapper(ObjectMapper), configure(...), addModule(...) and reset() per class (shared MapperHolder). - ModelConverters: rebuilds only its default ModelResolver in place when the factory generation moves, keeping user-added converters. - Fix stale _intr() javadoc in AbstractModelConverter. - Tests: MapperCustomizerTest, ModelConvertersCustomizerTest. Jackson 3 follow-up to swagger-api#4052 / swagger-api#4053; related swagger-api#3837, swagger-api#4991, swagger-api#5314. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Targets the
3.0.0branch (Jackson 3).Problem
Jackson 3
ObjectMappers are immutable. With Jackson 2, the documented way to make swagger-core aware of Kotlin/Scala/Jakarta XML Bind/Guava/… types wasand it worked for two reasons:
Json.mapper()was mutable, andAbstractModelConverterkept the same instance, so the defaultModelResolverinModelConverterssaw the module too (that is what drives model introspection; see the_intr()comment about users loading modules later).On
3.0.0both are gone:Json/Yaml/Json31/Yaml31hold astatic finalmapper with no way to replace it, andAbstractModelConverternowrebuild()s a private copy, so even a swappedJson.mapper()would not reach an already-created resolver. The only surviving hook isObjectMapperProcessor, which is scoped to anOpenApiContextand appends an extra resolver rather than configuring the ~80 internalJson.mapper()call sites (ModelDeserializer,AnnotationsUtils,SwaggerSerializers, plugins, …).This is the Jackson 3 version of what I proposed in #4052 / draft PR #4053 (Jackson 2, builder-based
ObjectMapperFactorywith a user-modifiable configuration and reset). Related: #3837 (findAndRegisterModulesrequest), #4991 (Jackson 3 support), #5314 (the Jackson 3 migration this builds on).Changes
ObjectMapperFactorycustomizer registry (new,swagger-core)MapperCustomizer((MapperBuilder<?,?>, MapperTarget) -> void) andMapperTargetenum (JSON,YAML,JSON31,YAML31,JSON_CONVERTER).addCustomizer/removeCustomizer/clearCustomizers/getCustomizers,addModule(s)shortcuts, and ageneration()counter.create(...)andcreateJsonConverter(), after swagger-core's modules/mixins/inclusion settings, so user config wins on conflicts — same precedenceregisterModule-after-construction had.buildStrictGenericObjectMapper()is intentionally not customized.Json/Yaml/Json31/Yaml31become replaceablemapper()is unchanged in signature but is now lazily built and rebuilt when the factory generation moves, so a customizer registered at any time takes effect. New per-mapper API for the "only this one" case:Json31.converterMapper()gets the same treatment (+converterMapper(ObjectMapper)). The four classes share a package-privateMapperHolder.ModelConverterskeeps its defaultModelResolverin syncThis is the part that actually restores Jackson 2 behaviour for model introspection.
ModelConvertersremembers how it built its default resolver and, whenObjectMapperFactory.generation()has moved, replaces only that resolver in place on the nextread/readAll/resolveAsResolvedSchema/getConverterscall. User-added converters, skipped packages/classes and the singleton itself are untouched. The stale_intr()javadoc inAbstractModelConverteris updated to describe this.ObjectMapperProcessoris unchanged and composes with this:IntegrationObjectMapperFactory.createJson()now includes registered customizers.Migration (Jackson 2 → 3.0.0)
Json.mapper().registerModule(m)+ same onYaml/Json31/Yaml31ObjectMapperFactory.addModule(m)Json.mapper().registerModule(m)(JSON only, on purpose)Json.addModule(m)Json.mapper().setSerializationInclusion(...)/.configure(...)/.addMixIn(...)Json.configure(b -> ...)new ModelResolver(myMapper)Note for the migration guide: Jackson 3 databind bundles JSR-310, JDK8
Optionaland parameter-names support, soJavaTimeModule— historically the #1 reason to callregisterModule— is no longer needed.Tests
MapperCustomizerTest(11): module reaches all five targets; customizer ordering (user overridesNON_NULL); registering after first use rebuilds and re-caches; remove/clear; strict generic mapper untouched; per-mapperaddModule/configurekeep swagger mixins; explicitmapper(ObjectMapper)survives factory changes untilreset();Json31.reset()also resets the converter mapper.ModelConvertersCustomizerTest(4): aSNAKE_CASEnaming strategy registered afterModelConverters.getInstance()changes resolved property names (3.0 and 3.1 instances); user-added converter keeps its position across the refresh; removing the customizer restores defaults.All new public classes/methods carry
@since 3.0.0. Everything added is new static/instance API — no signature changes on the3.0.0branch.Not in this PR (possible follow-ups):
ServiceLoaderdiscovery ofMapperCustomizer, and ajacksonModulesoption for the maven/gradle plugins.🤖 Generated with Claude Code