Skip to content

WW-5748 fix(rest): let Jackson XML's deserializer modifier see the bean deserializer - #1947

Merged
lukaszlenart merged 1 commit into
mainfrom
WW-5748
Sep 15, 2026
Merged

lukaszlenart merged 1 commit into
mainfrom
WW-5748

Conversation

@lukaszlenart

Copy link
Copy Markdown
Member

Fixes WW-5748

Problem

JacksonXmlHandler registered ParameterAuthorizingModule on an XmlMapper whose constructor had already registered JacksonXmlModule. A module's deserializer modifier is inserted at the head of the list, so the authorizing modifier ran first and handed Jackson XML's modifier a RedactionAwareDeserializer, which fails its instanceof BeanDeserializerBase test. The XML wrapper that reads an unwrapped list (@JacksonXmlElementWrapper(useWrapping = false)) was never installed, and every such list failed to deserialize through the XML handler — authorization context or not — since the module was introduced in 7.2.0.

Change

  • The handler builds the XmlMapper without a module and registers JacksonXmlModule after ParameterAuthorizingModule, so the XML modifier runs first and the authorizing wrapper goes around its result. Same XmlFactory defaults (external entities and DTD support off) as the default constructor. The module's Javadoc states the order for handlers that register it themselves.
  • With Jackson XML's wrapper now inside the authorizing one, the per-property @JsonIdentityInfo reader rebuild in RedactionAwareDeserializer.createContextual (WW-5746) walks delegating wrappers down to the bean. Jackson XML's wrapper cannot take a new delegatee, so it is rebuilt around the bean and contextualized with a null property, which recomputes its unwrapped names without building the id reader over again. The wrapper only stays around a bean that has an unwrapped list, so the test bean carries both.
  • jackson-dataformat-xml is optional for the plugin: the class naming its wrapper (XmlWrapperSupport.Xml) is loaded only once Class.forName has confirmed it is present; the JSON path never reaches it.

Tests

JacksonXmlHandlerTest (existing class, extended): unwrapped list without a context, authorized, rejected; @JacksonXmlText with an element sibling; sole text with an attribute (Jackson XML's text deserializer, also newly reachable) read and authorized; per-property bean-typed @JsonIdentityInfo on a bean with an unwrapped list, authorized under child.id.k. The two XML any-setter tests in ParameterAuthorizingModuleTest now build their mapper the way the handler does.

mvn test -DskipAssembly -pl plugins/rest: 216 tests, 0 failures. One code-review pass and one security pass (module order vs. property wrapping, XML renames, resync under virtual wrapping, the null-property re-contextualization, the optional-dependency guard under a class loader without the XML jar, XmlFactory defaults): nothing newly introduced.

🤖 Generated with Claude Code

…an deserializer

JacksonXmlHandler registered ParameterAuthorizingModule on an XmlMapper
whose constructor had already registered JacksonXmlModule. A module's
deserializer modifier is inserted at the head of the list, so the
authorizing modifier ran first and handed Jackson XML's modifier a
RedactionAwareDeserializer, which fails its instanceof
BeanDeserializerBase test: the XML wrapper that reads an unwrapped list
(@JacksonXmlElementWrapper(useWrapping = false)) was never installed,
and every such list failed to deserialize through the XML handler,
authorization context or not, since the module was introduced.

The handler now builds the XmlMapper without a module and registers
JacksonXmlModule after ParameterAuthorizingModule, so the XML modifier
runs first and the authorizing wrapper goes around its result. The
module's Javadoc states the order for handlers that register it
themselves.

With Jackson XML's wrapper now inside the authorizing one, the
per-property @JsonIdentityInfo reader rebuild in
RedactionAwareDeserializer.createContextual (WW-5746) walks the
delegating wrappers down to the bean. Jackson XML's wrapper cannot take
a new delegatee, so it is rebuilt around the bean and contextualized
with a null property, which recomputes its unwrapped names without
building the id reader over again; other delegating wrappers get the
rebuilt bean through replaceDelegatee. The wrapper only stays around a
bean that has an unwrapped list, so the combination is exercised by a
bean carrying both. jackson-dataformat-xml is optional for the plugin,
so the class naming its wrapper is loaded only once it is known to be
present.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

@lukaszlenart
lukaszlenart marked this pull request as ready for review September 15, 2026 16:45
@lukaszlenart
lukaszlenart merged commit 349314a into main Sep 15, 2026
13 checks passed
@lukaszlenart
lukaszlenart deleted the WW-5748 branch September 15, 2026 16:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant