Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ Currently, following limitations exist beyond general Jackson (JSON) limitations
* Note: over time some level of support has been added, and `Collection`s, for example, often work.
* Lists and arrays are "wrapped" by default, when using Jackson annotations, but unwrapped when using JAXB annotations (if supported, see below)
* `@JacksonXmlElementWrapper.useWrapping` can be set to 'false' to disable wrapping
* `JacksonXmlModule.setDefaultUseWrapper()` can be used to specify whether "wrapped" or "unwrapped" setting is the default
* `XmlMapper.builder().defaultUseWrapper()` can be used to specify whether "wrapped" or "unwrapped" setting is the default
* Polymorphic Type Handling works, but only some inclusion mechanisms are supported (`WRAPPER_ARRAY`, for example is not supported due to problems with reference to mapping of XML, Arrays)
* JAXB-style "compact" Type Id where property name is replaced with Type Id is not supported.
* Mixed Content (elements and text in same element) is not supported in databinding: child content must be either text OR element(s) (attributes are fine)
Expand Down
3 changes: 3 additions & 0 deletions release-notes/CREDITS
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,9 @@ Christian Beikov (@beikov)
* Fixed #911: Verify Stax factory type before instantiating in
`XmlFactory.readResolve()`
(3.1.7)
* Fixed #913: `XmlMapper.Builder.defaultUseWrapper()` changes mapper that
builder was created from (via `rebuild()`), or has already built
(3.3.0)
* Fixed #914: `XmlFactoryBuilder` `enable()`/`disable()`/`configure()` for
`XmlReadFeature`, `XmlWriteFeature` have no effect on `XmlFactory` built
(3.1.7)
Expand Down
3 changes: 3 additions & 0 deletions release-notes/VERSION
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ Version: 3.x (for earlier see VERSION-2.x)
#909: Clear forced `xsi:type` attribute state in `ToXmlGenerator.writeName()`
(`Map`/`JsonNode` key `xsi:type` leaked attribute mode onto following siblings)
(fix by @Sahana2524)
#913: `XmlMapper.Builder.defaultUseWrapper()` changes mapper that builder was
created from (via `rebuild()`), or has already built
(fix by @Sahana2524)

3.2.3 (21-Sep-2026)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import tools.jackson.databind.PropertyName;
import tools.jackson.databind.cfg.MapperConfig;
import tools.jackson.databind.introspect.*;
import tools.jackson.databind.util.ClassUtil;
import tools.jackson.dataformat.xml.annotation.*;

/**
Expand Down Expand Up @@ -55,16 +56,54 @@ public JacksonXmlAnnotationIntrospector(boolean defaultUseWrapper) {
_cfgDefaultUseWrapper = defaultUseWrapper;
}

/**
* Copy constructor for sub-classes to use when overriding
* {@link #withDefaultUseWrapper}: copies settings of {@code src} other
* than default for List wrapping, which is set to given value.
*
* @since 3.3
*/
protected JacksonXmlAnnotationIntrospector(JacksonXmlAnnotationIntrospector src,
boolean defaultUseWrapper)
{
super(src);
_cfgDefaultUseWrapper = defaultUseWrapper;
}

/*
/**********************************************************************
/* Extended API XML format module requires
/**********************************************************************
*/

/**
* @deprecated Since 3.3 use {@link #withDefaultUseWrapper} instead: modifying
* an introspector in place also affects any mapper that is already using it
* (including ones created via {@code XmlMapper.rebuild()})
*/
@Deprecated // since 3.3
public void setDefaultUseWrapper(boolean b) {
_cfgDefaultUseWrapper = b;
}

/**
* Sub-classes MUST override this method (usually using copy constructor
* {@link #JacksonXmlAnnotationIntrospector(JacksonXmlAnnotationIntrospector, boolean)})
* to retain their type and settings; otherwise an {@link IllegalStateException}
* is thrown when a re-configured copy would be needed.
*
* @since 3.3
*/
@Override
public JacksonXmlAnnotationIntrospector withDefaultUseWrapper(boolean b) {
if (_cfgDefaultUseWrapper == b) {
return this;
}
ClassUtil.verifyMustOverride(JacksonXmlAnnotationIntrospector.class, this,
"withDefaultUseWrapper");
return new JacksonXmlAnnotationIntrospector(this, b);
}

/*
/**********************************************************************
/* Overrides of JacksonAnnotationIntrospector impls
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import tools.jackson.databind.cfg.MapperConfig;
import tools.jackson.databind.introspect.Annotated;
import tools.jackson.databind.introspect.AnnotationIntrospectorPair;
import tools.jackson.databind.util.ClassUtil;

/**
* Additional extension interface used above and beyond
Expand All @@ -13,6 +14,32 @@
public interface XmlAnnotationIntrospector
extends AnnotationIntrospector.XmlExtensions
{
/**
* Mutant factory for getting an introspector that uses given default for
* List wrapping (for Lists and arrays without explicit wrapper annotation):
* returns this instance if there is no change (or no such setting), a
* re-configured copy otherwise. Must never modify this instance, as an
* introspector may be shared by multiple (immutable) mappers; see
* {@code XmlMapper.rebuild()}.
*<p>
* Default implementation returns {@code this}, for introspectors that have
* no such setting.
*<p>
* NOTE: implementations are expected to extend {@link AnnotationIntrospector},
* and value returned MUST also be an {@link AnnotationIntrospector} (since it
* is used as the replacement introspector by
* {@code XmlMapper.Builder.defaultUseWrapper()}).
*
* @param defaultUseWrapper Whether to use wrapping by default or not
*
* @return Introspector that uses given default for wrapping
*
* @since 3.3
*/
default XmlAnnotationIntrospector withDefaultUseWrapper(boolean defaultUseWrapper) {
return this;
}

/*
/**********************************************************************
/* Replacement of 'AnnotationIntrospector.Pair' to use when combining
Expand Down Expand Up @@ -51,6 +78,35 @@ public Pair(AnnotationIntrospector p, AnnotationIntrospector s)
public static XmlAnnotationIntrospector.Pair instance(AnnotationIntrospector a1, AnnotationIntrospector a2) {
return new XmlAnnotationIntrospector.Pair(a1, a2);
}

/**
* Sub-classes MUST override this method to retain their type; otherwise
* an {@link IllegalStateException} is thrown when a re-configured copy
* would be needed.
*
* @since 3.3
*/
@Override
public XmlAnnotationIntrospector withDefaultUseWrapper(boolean defaultUseWrapper)
{
AnnotationIntrospector p = _withDefaultUseWrapper(_primary, defaultUseWrapper);
AnnotationIntrospector s = _withDefaultUseWrapper(_secondary, defaultUseWrapper);
if ((p == _primary) && (s == _secondary)) {
return this;
}
ClassUtil.verifyMustOverride(XmlAnnotationIntrospector.Pair.class, this,
"withDefaultUseWrapper");
return new XmlAnnotationIntrospector.Pair(p, s);
}

protected static AnnotationIntrospector _withDefaultUseWrapper(AnnotationIntrospector ai,
boolean defaultUseWrapper)
{
if (ai instanceof XmlAnnotationIntrospector xmlAi) {
return (AnnotationIntrospector) xmlAi.withDefaultUseWrapper(defaultUseWrapper);
}
return ai;
}

@Override
public String findNamespace(MapperConfig<?> config, Annotated ann)
Expand Down
33 changes: 25 additions & 8 deletions src/main/java/tools/jackson/dataformat/xml/XmlMapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -276,16 +276,33 @@ public boolean defaultUseWrapper() {
* Jackson annotations have different default due to backwards compatibility.
*/
public Builder defaultUseWrapper(boolean b) {
if (_defaultUseWrapper != b) {
_defaultUseWrapper = b;

AnnotationIntrospector ai0 = annotationIntrospector();
for (AnnotationIntrospector ai : ai0.allIntrospectors()) {
if (ai instanceof JacksonXmlAnnotationIntrospector xmlAi) {
xmlAi.setDefaultUseWrapper(b);
}
// Introspector may be shared with the mapper this builder was created
// from (see `XmlMapper.rebuild()`), as well as with mappers it has
// already built: so must not modify it in place but swap in
// re-configured copy (same as `nameForTextElement()` does for factory).
// NOTE: done even if builder setting is unchanged, since introspector
// may have been replaced with one that uses a different setting
AnnotationIntrospector ai = annotationIntrospector();
AnnotationIntrospector newAi = (ai instanceof XmlAnnotationIntrospector xmlAi)
? (AnnotationIntrospector) xmlAi.withDefaultUseWrapper(b)
: ai;
// But not all introspectors can be re-configured (without changing
// structure), like ones within databind-provided `AnnotationIntrospectorPair`:
// must fail if any of those would still need change
for (AnnotationIntrospector curr : newAi.allIntrospectors()) {
if ((curr instanceof XmlAnnotationIntrospector xmlAi)
&& (xmlAi.withDefaultUseWrapper(b) != xmlAi)) {
throw new IllegalStateException(String.format(
"Cannot change `defaultUseWrapper` of `%s`: it is contained in a pair other than `%s` (like databind `AnnotationIntrospectorPair`); combine all introspectors using `%s` instead",
curr.getClass().getName(),
XmlAnnotationIntrospector.Pair.class.getName(),
XmlAnnotationIntrospector.Pair.class.getName()));
}
}
if (newAi != ai) {
annotationIntrospector(newAi);
}
_defaultUseWrapper = b;
return this;
}

Expand Down
Loading
Loading