Skip to content

Apply XmlNameProcessor consistently to all written element/attribute names - #918

Merged
cowtowncoder merged 14 commits into
FasterXML:3.xfrom
Sahana2524:wrapper-name-encode
Oct 26, 2026
Merged

cowtowncoder merged 14 commits into
FasterXML:3.xfrom
Sahana2524:wrapper-name-encode

Conversation

@Sahana2524

@Sahana2524 Sahana2524 commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

Collection wrapper element name skips the XmlNameProcessor

startWrappedValue() writes the wrapper element straight to the Stax writer, so with a non-passthrough processor the wrapper name comes out verbatim while the wrapped item names (which go through writeName()) are encoded. Since the reader decodes every element name, a default wrapped List serialized with newAlwaysOnBase64Processor() then throws StreamReadException on read (the raw wrapper name is not valid base64), and a wrapper name holding an XML-invalid character such as a space emits non-well-formed XML. Passthrough output is unchanged.

Scope grew during review: names encoded at a single point

The same class of bug existed for other names that reached the Stax writer without going through the processor, so the fix was generalized:

  • Single encoding point. ToXmlGenerator._encodeName() is now the one place names are encoded. setNextName() / setNextNameIfMissing() encode their argument, as do writeName() and the wrapper name in startWrappedValue(). _nextName always holds the name as written.
  • Root element names (class name, @JsonRootName, ObjectWriter.withRootName()) are now encoded; previously e.g. a class Point broke the AlwaysOn base64 round trip. tofix/RootNameEncodingTest moved to misc/. The default namespace set in _initWithRootName() follows the encoded name.
  • Root attributes added via XmlGeneratorInitializer.addRootAttribute() are now encoded, so they can be read back like other attributes.
  • Standard-namespace names are never encoded or decoded. Names in the xsi, xml and xmlns namespaces are a fixed vocabulary: _encodeName() leaves them as-is, and XmlTokenStream._decodeName() no longer decodes xsi/xml names (StAX never reports xmlns declarations as attributes). Literal attributes such as xsi:noNamespaceSchemaLocation or xml:lang, and namespace declarations added as root attributes, stay usable by other XML tools and no longer fail with a name-decode error under base64 processors. writeName() also no longer carries these namespaces over from a preceding property to content names (Map / @JsonAnyGetter keys), which would have skipped their encoding.
  • Each name encoded once per write. The generator remembers the unencoded name next to _nextName, so setNextName() followed by writeName() of the same name does not encode twice, and namespaces are not processed twice; collection items reuse the encoded wrapper name when both are the same. This shortcut only applies while _nextName is still the exact name encoded from it, so direct assignments (subclasses, internal restores) cannot leave it stale. Bean serializers pass the unencoded property name and let the following writeName() encode it, so properties that end up not written (nulls, filtered out) are never encoded. The stream write context tracks unencoded names.

API / behavior changes (3.3.0, unreleased)

  • ToXmlGenerator.encodeContentName() (public, added in Apply XmlNameProcessor to unwrapped ObjectNode root element name #887, never released) is replaced by protected _encodeName(). Callers now always pass unencoded names, which removes the risk of double encoding.
  • With a non-passthrough XmlNameProcessor, names passed to setNextName() / setNextNameIfMissing() by custom serializers, and root attribute names, are now encoded. Before, they were written verbatim and could not be read back.
  • Names in the xsi, xml and xmlns namespaces (including bean properties explicitly declared in them) are no longer encoded on write or decoded on read. Content names following such a property no longer inherit its namespace. Compatibility note: with the AlwaysOn base64 processor, 3.2 wrote bean properties explicitly declared in the xsi or xml namespace with encoded names (e.g. xml:bGFuZw); 3.3 reads such names literally, so those properties will not match when reading XML written by 3.2. (The plain Base64 processor leaves valid names unchanged, so it is not affected for them.)

Tests (in XmlNameEscapeTest and RootNameEncodingTest)

  • AlwaysOn base64 round trips: wrapped collection (compact and pretty-printed), a non-base64-safe root name, and names set directly on the generator (setNextName(), startWrappedValue(), writeRepeatedPropertyName()).
  • Root attribute round trip, with xsi:, xml:lang and xmlns: attributes left literal; reading works with and without SKIP_UNKNOWN_XSI_ATTRIBUTES.
  • A content name after an xsi-namespaced property is encoded (Replacement processor).
  • Replacement processor output read back with the configured mapper, so the replaced wrapper must still be read as a wrapper.
  • A counting processor checks that each name is encoded exactly once per write (including a namespaced root and wrapped collections), and that unwritten properties are not encoded.
  • setNextName(null) still clears the pending name.

🤖 Generated with Claude Code

@github-actions

Copy link
Copy Markdown

🧪 Code Coverage Report

Metric Coverage Change
Instructions coverage 77.70% 📈 +0.010%
Branches branches 70.97% 📈 +0.000%

Coverage data generated from JaCoCo test results

1 similar comment
@github-actions

Copy link
Copy Markdown

🧪 Code Coverage Report

Metric Coverage Change
Instructions coverage 77.70% 📈 +0.010%
Branches branches 70.97% 📈 +0.000%

Coverage data generated from JaCoCo test results

@cowtowncoder cowtowncoder changed the title encode collection wrapper element name via XmlNameProcessor Encode collection wrapper element name via XmlNameProcessor Sep 28, 2026
@github-actions

Copy link
Copy Markdown

🧪 Code Coverage Report

Metric Coverage Change
Instructions coverage 77.70% 📈 +2.750%
Branches branches 70.97% 📈 +1.470%

Coverage data generated from JaCoCo test results

@github-actions

github-actions Bot commented Oct 6, 2026

Copy link
Copy Markdown

🧪 Code Coverage Report

Metric Coverage Change
Instructions coverage 77.70% 📈 +0.010%
Branches branches 70.97% 📈 +0.000%

Coverage data generated from JaCoCo test results

@github-actions

github-actions Bot commented Oct 6, 2026

Copy link
Copy Markdown

🧪 Code Coverage Report

Metric Coverage Change
Instructions coverage 77.70% 📈 +0.010%
Branches branches 70.97% 📈 +0.000%

Coverage data generated from JaCoCo test results

@github-actions

github-actions Bot commented Oct 6, 2026

Copy link
Copy Markdown

🧪 Code Coverage Report

Metric Coverage Change
Instructions coverage 77.70% 📈 +0.010%
Branches branches 70.97% 📈 +0.000%

Coverage data generated from JaCoCo test results

@Sahana2524

Copy link
Copy Markdown
Contributor Author

any update?

@github-actions

Copy link
Copy Markdown

🧪 Code Coverage Report

Metric Coverage Change
Instructions coverage 77.70% 📈 +0.010%
Branches branches 70.97% 📈 +0.000%

Coverage data generated from JaCoCo test results

Root names from class name, @JsonRootName or withRootName() were written
without going through the XmlNameProcessor, so AlwaysOn base64 round trips
failed for most POJOs. Encode in _initWithRootName() (covering all root
paths, including unwrapped ObjectNode) and move RootNameEncodingTest out
of tofix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

🧪 Code Coverage Report

Metric Coverage Change
Instructions coverage 77.71% 📈 +0.020%
Branches branches 70.97% 📈 +0.000%

Coverage data generated from JaCoCo test results

…asterXML#918)

Names passed via setNextName()/setNextNameIfMissing(), writeName() and
the startWrappedValue() wrapper now all go through one helper,
_encodeName(), so _nextName always holds the encoded name. Fixes item
names set via startWrappedValue() (used with writeRepeatedPropertyName())
being written unencoded, and avoids double encoding by replacing the
(unreleased) public encodeContentName() with protected _encodeName().
Reuses existing QName when processor leaves name unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

🧪 Code Coverage Report

Metric Coverage Change
Instructions coverage 77.78% 📈 +0.090%
Branches branches 71.09% 📈 +0.120%

Coverage data generated from JaCoCo test results

…h configured mapper

- Add indented variant of AlwaysOn base64 wrapped collection round trip,
  covering wrapper start element written via XmlPrettyPrinter
- Read Replacement processor output back with the configured mapper into
  a DTO declaring replaced names, verifying wrapper is recognized on read
- Import JacksonXmlElementWrapper; reference FasterXML#918 in test comments

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

🧪 Code Coverage Report

Metric Coverage Change
Instructions coverage 77.78% 📈 +0.090%
Branches branches 71.09% 📈 +0.120%

Coverage data generated from JaCoCo test results

…asterXML#918)

Keep the unencoded name next to _nextName (_nextNameRaw), so that:
- writeName() does not re-encode the name a bean serializer already set
  via setNextName() (each name now encoded once per write, not twice)
- namespace carried over by writeName() is the unencoded one
- writeRepeatedPropertyName() registers unencoded name in stream context,
  same as writeName()

Also:
- Encode root attribute names (except XSI ones meant for other tools)
- Set default namespace from encoded root name in _initWithRootName()
- Preserve prefix when re-creating encoded QName; simplify _encodeName()
- Correct _nextName javadoc (fixed names are not encoded)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

🧪 Code Coverage Report

Metric Coverage Change
Instructions coverage 77.87% 📈 +0.180%
Branches branches 71.16% 📈 +0.190%

Coverage data generated from JaCoCo test results

… names (FasterXML#918)

- Only trust _nextNameRaw while _nextName is still the QName encoded from
  it (_nextNameEncoded), so direct assignments to _nextName (subclasses,
  internal restores) cannot leave a stale raw name; drops manual resets
- XSI namespace names are standard names: ToXmlGenerator._encodeName()
  leaves them as-is and XmlTokenStream._decodeName() no longer decodes
  them, so literal xsi:* attributes (e.g. xsi:noNamespaceSchemaLocation)
  no longer fail to decode with base64 name processors

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

🧪 Code Coverage Report

Metric Coverage Change
Instructions coverage 77.89% 📈 +0.200%
Branches branches 71.22% 📈 +0.250%

Coverage data generated from JaCoCo test results

@cowtowncoder cowtowncoder changed the title Encode collection wrapper element name via XmlNameProcessor Apply XmlNameProcessor consistently to all written element/attribute names Oct 26, 2026
…asterXML#918)

- Allow setNextName(null) / setNextNameIfMissing(null) again (clears name)
- Bean serializers set unencoded property name via _setNextPropertyName();
  the following writeName() encodes it, so names of properties not written
  (nulls, filtered out) are no longer encoded
- _initWithRootName(): take default namespace from the name actually set,
  instead of encoding root name a second time
- Never encode/decode names in the standard xml: namespace (xml:lang etc),
  same as XSI names
- Rename _nextNameRaw() accessor to _validNextNameRaw()

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

🧪 Code Coverage Report

Metric Coverage Change
Instructions coverage 77.89% 📈 +0.200%
Branches branches 71.28% 📈 +0.310%

Coverage data generated from JaCoCo test results

…encodes (FasterXML#918)

- Never encode names in the xmlns namespace (namespace declarations given
  as root attributes), same as XSI and xml: names
- writeName() no longer carries over XSI/xml/xmlns namespace of previous
  name to content names (which also skipped their encoding)
- Bean property name pending encoding is encoded by writeName() without
  allocating another QName
- startWrappedValue(): reuse encoded wrapper name for items when same
- Javadoc: _nextName is unencoded between _setNextPropertyName() and
  writeName()

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

🧪 Code Coverage Report

Metric Coverage Change
Instructions coverage 77.97% 📈 +0.280%
Branches branches 71.38% 📈 +0.410%

Coverage data generated from JaCoCo test results

…terXML#918)

- Rename package-private _setNextPropertyName() to setNextPropertyName()
- startWrappedValue(): accept null wrappedName again (clears name)
- _validNextNameRaw(): never consider raw name valid for null _nextName
- Factor out _setNextName(raw, encoded); fix _nextIsXsiType javadoc

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

🧪 Code Coverage Report

Metric Coverage Change
Instructions coverage 77.98% 📈 +0.290%
Branches branches 71.34% 📈 +0.370%

Coverage data generated from JaCoCo test results

@cowtowncoder
cowtowncoder merged commit 2c0855d into FasterXML:3.x Oct 26, 2026
3 checks passed
@cowtowncoder cowtowncoder added this to the 2, milestone Oct 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants