Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -62,47 +62,11 @@ public abstract class XmlBeanSerializerBase extends BeanSerializerBase
public XmlBeanSerializerBase(BeanSerializerBase src)
{
super(src);

// Then make sure attributes are sorted before elements, keep track
// of how many there are altogether
int attrCount = 0;
for (BeanPropertyWriter bpw : _props) {
if (_isAttribute(bpw)) { // Yup: let's build re-ordered list then
attrCount = _orderAttributesFirst(_props, _filteredProps);
break;
}
}
_attributeCount = attrCount;

// also: pre-compute need, if any, for CDATA handling:
BitSet cdata = null;
for (int i = 0, len = _props.length; i < len; ++i) {
BeanPropertyWriter bpw = _props[i];
if (_isCData(bpw)) {
if (cdata == null) {
cdata = new BitSet(len);
}
cdata.set(i);
}
}
_cdata = cdata;

// And then collect namespace information
_xmlNames = new QName[_props.length];
int textIndex = -1;
for (int i = 0, len = _props.length; i < len; ++i) {
BeanPropertyWriter bpw = _props[i];
XmlInfo info = (XmlInfo) bpw.getInternalSetting(KEY_XML_INFO);
String ns = null;
if (info != null) {
ns = info.getNamespace();
if (textIndex < 0 && info.isText()) {
textIndex = i;
}
}
_xmlNames[i] = new QName((ns == null) ? "" : ns, bpw.getName());
}
_textPropertyIndex = textIndex;
XmlInfoArrays info = _resolveXmlInfo(_props, _filteredProps);
_attributeCount = info.attributeCount;
_textPropertyIndex = info.textPropertyIndex;
_xmlNames = info.xmlNames;
_cdata = info.cdata;
}

protected XmlBeanSerializerBase(XmlBeanSerializerBase src, ObjectIdWriter objectIdWriter)
Expand All @@ -127,12 +91,16 @@ protected XmlBeanSerializerBase(XmlBeanSerializerBase src,
Set<String> toIgnore, Set<String> toInclude)
{
super(src, toIgnore, toInclude);
_attributeCount = src._attributeCount;
_textPropertyIndex = src._textPropertyIndex;
_xmlNames = src._xmlNames;
_cdata = src._cdata;
// 30-Aug-2026: `super(...)` drops the ignored/non-included writers and
// re-indexes `_props`, so the per-index XML metadata can not be copied
// from `src`; recompute it against the new property array.
XmlInfoArrays info = _resolveXmlInfo(_props, _filteredProps);
_attributeCount = info.attributeCount;
_textPropertyIndex = info.textPropertyIndex;
_xmlNames = info.xmlNames;
_cdata = info.cdata;
}

public XmlBeanSerializerBase(XmlBeanSerializerBase src, NameTransformer transformer)
{
super(src, transformer);
Expand All @@ -145,10 +113,83 @@ public XmlBeanSerializerBase(XmlBeanSerializerBase src, NameTransformer transfor
protected XmlBeanSerializerBase(XmlBeanSerializerBase src,
BeanPropertyWriter[] properties, BeanPropertyWriter[] filteredProperties) {
super(src, properties, filteredProperties);
_attributeCount = src._attributeCount;
_textPropertyIndex = src._textPropertyIndex;
_xmlNames = src._xmlNames;
_cdata = src._cdata;
// A modifier may hand us a different (re-ordered or filtered) set of
// writers, so the per-index XML metadata has to be recomputed to stay
// aligned rather than copied from `src`.
XmlInfoArrays info = _resolveXmlInfo(_props, _filteredProps);
_attributeCount = info.attributeCount;
_textPropertyIndex = info.textPropertyIndex;
_xmlNames = info.xmlNames;
_cdata = info.cdata;
}

/**
* Holder for the four index-parallel structures that describe XML output of
* a set of properties: number of leading attributes, index of the text
* ("unwrapped") property, per-index element/attribute {@link QName}s, and the
* set of indexes to write as CDATA.
*/
private static final class XmlInfoArrays {
final int attributeCount;
final int textPropertyIndex;
final QName[] xmlNames;
final BitSet cdata;

XmlInfoArrays(int attributeCount, int textPropertyIndex,
QName[] xmlNames, BitSet cdata) {
this.attributeCount = attributeCount;
this.textPropertyIndex = textPropertyIndex;
this.xmlNames = xmlNames;
this.cdata = cdata;
}
}

/**
* Derives the XML-specific, index-parallel metadata from the given property
* arrays. Attributes are re-ordered to the front of {@code props} (and
* {@code filteredProps}) as a side effect, matching prior behavior.
*/
private static XmlInfoArrays _resolveXmlInfo(BeanPropertyWriter[] props,
BeanPropertyWriter[] filteredProps)
{
// Then make sure attributes are sorted before elements, keep track
// of how many there are altogether
int attrCount = 0;
for (BeanPropertyWriter bpw : props) {
if (_isAttribute(bpw)) { // Yup: let's build re-ordered list then
attrCount = _orderAttributesFirst(props, filteredProps);
break;
}
}

// also: pre-compute need, if any, for CDATA handling:
BitSet cdata = null;
for (int i = 0, len = props.length; i < len; ++i) {
BeanPropertyWriter bpw = props[i];
if (_isCData(bpw)) {
if (cdata == null) {
cdata = new BitSet(len);
}
cdata.set(i);
}
}

// And then collect namespace information
QName[] xmlNames = new QName[props.length];
int textIndex = -1;
for (int i = 0, len = props.length; i < len; ++i) {
BeanPropertyWriter bpw = props[i];
XmlInfo info = (XmlInfo) bpw.getInternalSetting(KEY_XML_INFO);
String ns = null;
if (info != null) {
ns = info.getNamespace();
if (textIndex < 0 && info.isText()) {
textIndex = i;
}
}
xmlNames[i] = new QName((ns == null) ? "" : ns, bpw.getName());
}
return new XmlInfoArrays(attrCount, textIndex, xmlNames, cdata);
}

/*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package tools.jackson.dataformat.xml.ser;

import org.junit.jupiter.api.Test;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;

import tools.jackson.dataformat.xml.XmlMapper;
import tools.jackson.dataformat.xml.XmlTestUtil;
import tools.jackson.dataformat.xml.annotation.JacksonXmlCData;
import tools.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import tools.jackson.dataformat.xml.annotation.JacksonXmlText;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class IgnorePropsXmlInfoAlignmentTest extends XmlTestUtil
{
@JsonPropertyOrder({ "attr", "e1", "e2" })
static class AttrInner {
@JacksonXmlProperty(isAttribute = true)
public String attr = "A";
public String e1 = "E1";
public String e2 = "E2";
}

static class AttrOuter {
@JsonIgnoreProperties("attr")
public AttrInner inner = new AttrInner();
}

@JsonPropertyOrder({ "p0", "text", "p2" })
static class TextInner {
public String p0 = "P0";
@JacksonXmlText
public String text = "TEXT";
public String p2 = "P2";
}

static class TextOuter {
@JsonIgnoreProperties("p0")
public TextInner inner = new TextInner();
}

@JsonPropertyOrder({ "x", "cd", "y" })
static class CDataInner {
public String x = "X";
@JacksonXmlCData
public String cd = "C";
public String y = "Y";
}

static class CDataOuter {
@JsonIgnoreProperties("x")
public CDataInner inner = new CDataInner();
}

private final XmlMapper MAPPER = newMapper();

@Test
public void testAttributeStaysElementAfterIgnore() throws Exception
{
String xml = MAPPER.writeValueAsString(new AttrOuter());
assertEquals("<AttrOuter><inner><e1>E1</e1><e2>E2</e2></inner></AttrOuter>", xml);
}

@Test
public void testTextIndexAfterIgnore() throws Exception
{
String xml = MAPPER.writeValueAsString(new TextOuter());
assertEquals("<TextOuter><inner>TEXT<p2>P2</p2></inner></TextOuter>", xml);
}

@Test
public void testCDataIndexAfterIgnore() throws Exception
{
String xml = MAPPER.writeValueAsString(new CDataOuter());
assertEquals("<CDataOuter><inner><cd><![CDATA[C]]></cd><y>Y</y></inner></CDataOuter>", xml);
}
}
Loading