Skip to content

Commit ca45b98

Browse files
authored
Fix #455 (and possibly #426, #525, #567) (#812)
1 parent c405235 commit ca45b98

10 files changed

Lines changed: 192 additions & 64 deletions

File tree

.github/workflows/main.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919
strategy:
2020
fail-fast: false
2121
matrix:
22-
java_version: ['17', '21', '24' ]
22+
java_version: ['17', '21', '25' ]
2323
include:
2424
- java_version: '17'
2525
release_build: 'R'

release-notes/CREDITS

Lines changed: 39 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -6,61 +6,61 @@ XML format backend (`jackson-dataformat-xml`), version 3.x
66
Tatu Saloranta, tatu.saloranta@iki.fi: author
77

88
Morten Olav Hansen (mortenoh@github)
9-
10-
* Reported #25: `ACCEPT_EMPTY_STRING_AS_NULL_OBJECT` not honored in xml module
11-
for attributes
12-
(3.0.0)
9+
* Reported #25: `ACCEPT_EMPTY_STRING_AS_NULL_OBJECT` not honored in xml module
10+
for attributes
11+
(3.0.0)
1312

1413
Ghenadii Batalski (@ghenadiibatalski)
15-
16-
* Reported #793: `XmlSerializationContext`: NPE _writeCapabilities not set for
17-
`SerializationContext`
18-
(3.0.4)
14+
* Reported #793: `XmlSerializationContext`: NPE _writeCapabilities not set for
15+
`SerializationContext`
16+
(3.0.4)
1917

2018
PJ Fanning (@pjfanning)
21-
22-
* Fixed #793: `XmlSerializationContext`: NPE _writeCapabilities not set for
23-
`SerializationContext`
24-
(3.0.4)
19+
* Fixed #793: `XmlSerializationContext`: NPE _writeCapabilities not set for
20+
`SerializationContext`
21+
(3.0.4)
2522

2623
Dai MIKURUBE (@dmikurube)
27-
28-
* Reported #149: `@JacksonXmlElementWrapper` as a `@JsonCreator parameter` not working
29-
(3.2.0)
24+
* Reported #149: `@JacksonXmlElementWrapper` as a `@JsonCreator parameter` not working
25+
(3.2.0)
3026

3127
Christopher McVay (@mcvayc)
32-
33-
* Fixed #149: `@JacksonXmlElementWrapper` as a `@JsonCreator parameter` not working
34-
(3.2.0)
35-
* Fixed #517: XML wrapper doesn't work with java records
36-
(3.2.0)
37-
* Fixed #615: Deserialization of Xml with `@JacksonXmlText` using `@JsonCreator` (into
28+
* Fixed #149: `@JacksonXmlElementWrapper` as a `@JsonCreator parameter` not working
29+
(3.2.0)
30+
* Fixed #517: XML wrapper doesn't work with java records
31+
(3.2.0)
32+
* Fixed #615: Deserialization of Xml with `@JacksonXmlText` using `@JsonCreator` (into
3833
`java.util.Map`) fails
39-
(3.2.0)
40-
* Fixed #735: Java Record with `@JacksonXmlText` stopped working with 2.18
41-
(3.2.0)
42-
* Fixed #795: `HttpHeader` object (= <httpHeaders>) is not wrapped by the XML `<property>` tag
43-
(3.2.0)
44-
* Fixed #306: Can not use `@JacksonXmlText` for Creator property (creator parameter)
45-
(3.2.0)
34+
(3.2.0)
35+
* Fixed #735: Java Record with `@JacksonXmlText` stopped working with 2.18
36+
(3.2.0)
37+
* Fixed #795: `HttpHeader` object (= <httpHeaders>) is not wrapped by the XML `<property>` tag
38+
(3.2.0)
39+
* Fixed #306: Can not use `@JacksonXmlText` for Creator property (creator parameter)
40+
(3.2.0)
4641

4742
Eduard Wirch (@ewirch)
43+
* Reported #306: Can not use `@JacksonXmlText` for Creator property (creator parameter)
44+
(3.2.0)
4845

49-
* Reported #306: Can not use `@JacksonXmlText` for Creator property (creator parameter)
50-
(3.2.0)
46+
Jiri Mikulasek (@jimirocks)
47+
* Reported #455: Can't deserialize list in JsonSubtype when type property is visible
48+
(3.2.0)
5149

5250
Josip Antoliš (@Antolius)
51+
* Reported #517: XML wrapper doesn't work with java records
52+
(3.2.0)
5353

54-
* Reported #517: XML wrapper doesn't work with java records
55-
(3.2.0)
54+
Vitor Pamplona (@vitorpamplona)
55+
* Reported #525: Order of XML Properties trigger different behaviors with
56+
polymorphic nested objects
57+
(3.2.0)
5658

5759
Severin Kistler (@kistlers)
58-
59-
* Reported #615: Deserialization of Xml with `@JacksonXmlText` using `@JsonCreator` (into
60-
`java.util.Map`) fails
61-
(3.2.0)
60+
* Reported #615: Deserialization of Xml with `@JacksonXmlText` using `@JsonCreator` (into
61+
`java.util.Map`) fails
62+
(3.2.0)
6263

6364
Charles Moulliard (@cmoulliard)
64-
65-
* Reported #795: `HttpHeader` object (= <httpHeaders>) is not wrapped by the XML `<property>` tag
66-
(3.2.0)
65+
* Reported #795: `HttpHeader` object (= <httpHeaders>) is not wrapped by the XML `<property>` tag
66+
(3.2.0)

release-notes/VERSION

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,24 @@ Version: 3.x (for earlier see VERSION-2.x)
1515
#306: Can not use `@JacksonXmlText` for Creator property (creator parameter)
1616
(reported by Eduard W)
1717
(fix by Christopher M)
18+
#426: `InvalidTypeIdException` when parsing XML to POJO containing nested `List<>`,
19+
custom `TypeIdResolver`
20+
(reported by @ankagar)
21+
(fix by @cowtowncoder, w/ Claude code)
22+
#455: Can't deserialize list in JsonSubtype when type property is visible
23+
(reported by Jiri M)
24+
(fix by @cowtowncoder, w/ Claude code)
1825
#517: XML wrapper doesn't work with java records
1926
(reported by Josip A)
2027
(fix by Christopher M)
28+
#525: Order of XML Properties trigger different behaviors with
29+
polymorphic nested objects
30+
(reported by Vitor P)
31+
(fix by @cowtowncoder, w/ Claude code)
32+
#567: First element in unwrapped XML array is ignored during
33+
deserialization to base class
34+
(reported by @tetradon)
35+
(fix by @cowtowncoder, w/ Claude code)
2136
#615: Deserialization of Xml with `@JacksonXmlText` using `@JsonCreator` (into
2237
`java.util.Map`) fails
2338
(reported by Severin K)

src/main/java/tools/jackson/dataformat/xml/deser/FromXmlParser.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -290,11 +290,16 @@ public void addVirtualWrapping(Set<String> namesToWrap0, boolean caseInsensitive
290290
// problems with Lists-in-Lists properties
291291
// 12-May-2020, tatu: But as per [dataformat-xml#86] NOT for root element
292292
// (would still like to know why work-around needed ever, but...)
293+
// 15-Mar-2026, tatu: [dataformat-xml#455] Relax the parent-root check when
294+
// we're at an element's PROPERTY_NAME (_mayBeLeaf distinguishes elements
295+
// from attributes). This handles polymorphic type resolution where the type
296+
// deserializer consumed properties before wrapping was set up, so we need
297+
// to wrap the current element retroactively.
293298
if (!_streamReadContext.inRoot()
294-
&& !_streamReadContext.getParent().inRoot()) {
299+
&& (!_streamReadContext.getParent().inRoot()
300+
|| (_currToken == JsonToken.PROPERTY_NAME && _mayBeLeaf))) {
295301
String name = _xmlTokens.getLocalName();
296302
if ((name != null) && namesToWrap.contains(name)) {
297-
//System.out.println("REPEAT from addVirtualWrapping() for '"+name+"'");
298303
_xmlTokens.repeatStartElement();
299304
}
300305
}

src/main/java/tools/jackson/dataformat/xml/deser/WrapperHandlingDeserializer.java

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -147,13 +147,23 @@ public Object deserializeWithType(JsonParser p, DeserializationContext ctxt, Typ
147147
@SuppressWarnings("resource")
148148
protected final void _configureParser(JsonParser p) throws JacksonException
149149
{
150+
if (_namesToWrap == null) {
151+
return;
152+
}
150153
// 05-Sep-2019, tatu: May get XML parser, except for case where content is
151154
// buffered. In that case we may still have access to real parser if we
152155
// are lucky (like in [dataformat-xml#242])
153-
while (p instanceof JsonParserDelegate) {
154-
p = ((JsonParserDelegate) p).delegate();
156+
// 15-Mar-2026, tatu: [dataformat-xml#455] Check for ElementWrappable at
157+
// each delegation level, not just at the innermost parser. This handles
158+
// the case where XmlTokenBuffer wraps the TokenBuffer.Parser with an
159+
// ElementWrappable delegate during polymorphic type resolution.
160+
while (p instanceof JsonParserDelegate jpd) {
161+
if (p instanceof ElementWrappable) {
162+
break;
163+
}
164+
p = jpd.delegate();
155165
}
156-
if ((p instanceof ElementWrappable) && (_namesToWrap != null)) {
166+
if (p instanceof ElementWrappable ew) {
157167
// 03-May-2021, tatu: as per [dataformat-xml#469] there are special
158168
// cases where we get String token to represent XML empty element.
159169
// If so, need to refrain from adding wrapping as that would
@@ -165,17 +175,17 @@ protected final void _configureParser(JsonParser p) throws JacksonException
165175
// is consumed during buffering, so need to consider that too
166176
// it seems (just hope we are at correct level and not off by one...)
167177
|| t == JsonToken.PROPERTY_NAME) {
168-
((ElementWrappable) p).addVirtualWrapping(_namesToWrap, _caseInsensitive);
178+
ew.addVirtualWrapping(_namesToWrap, _caseInsensitive);
169179
}
170180
}
171181
}
172182

173183
protected BeanDeserializerBase _verifyDeserType(ValueDeserializer<?> deser)
174184
{
175-
if (!(deser instanceof BeanDeserializerBase)) {
185+
if (!(deser instanceof BeanDeserializerBase bdb)) {
176186
throw new IllegalArgumentException("Can not change delegate to be of type "
177187
+deser.getClass().getName());
178188
}
179-
return (BeanDeserializerBase) deser;
189+
return bdb;
180190
}
181191
}

src/main/java/tools/jackson/dataformat/xml/deser/XmlDeserializationContext.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import tools.jackson.databind.deser.DeserializationContextExt;
1111
import tools.jackson.databind.deser.DeserializerCache;
1212
import tools.jackson.databind.deser.DeserializerFactory;
13+
import tools.jackson.databind.util.TokenBuffer;
1314
import tools.jackson.dataformat.xml.XmlFactory;
1415

1516
/**
@@ -81,4 +82,17 @@ public String extractScalarFromObject(JsonParser p, ValueDeserializer<?> deser,
8182
}
8283
return text;
8384
}
85+
86+
/**
87+
* Override to return XML-aware {@link XmlTokenBuffer} that produces
88+
* parsers implementing {@link ElementWrappable}, allowing virtual wrapping
89+
* to be configured even after content has been buffered (e.g., during
90+
* polymorphic type resolution).
91+
*
92+
* @since 3.2
93+
*/
94+
@Override
95+
public TokenBuffer bufferForInputBuffering(JsonParser p) {
96+
return XmlTokenBuffer.xmlBufferForInputBuffering(p, this);
97+
}
8498
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package tools.jackson.dataformat.xml.deser;
2+
3+
import java.util.Set;
4+
5+
import tools.jackson.core.JsonParser;
6+
import tools.jackson.core.ObjectReadContext;
7+
import tools.jackson.core.util.JsonParserDelegate;
8+
9+
import tools.jackson.databind.util.TokenBuffer;
10+
11+
/**
12+
* XML-specific {@link TokenBuffer} sub-class that ensures parsers created
13+
* from buffered content implement {@link ElementWrappable}, allowing
14+
* virtual wrapping to be configured on the underlying XML parser even
15+
* when content has been buffered (e.g., during polymorphic type resolution).
16+
*
17+
* @since 3.2
18+
*/
19+
public class XmlTokenBuffer extends TokenBuffer
20+
{
21+
/**
22+
* Reference to the original XML parser that implements {@link ElementWrappable},
23+
* if one was found when this buffer was created.
24+
*/
25+
protected final ElementWrappable _wrappableParser;
26+
27+
protected XmlTokenBuffer(JsonParser p, ObjectReadContext ctxt)
28+
{
29+
super(p, ctxt);
30+
// Find the ElementWrappable parser by unwrapping delegates
31+
JsonParser unwrapped = p;
32+
while (unwrapped instanceof JsonParserDelegate del) {
33+
unwrapped = del.delegate();
34+
}
35+
_wrappableParser = (unwrapped instanceof ElementWrappable ew) ? ew : null;
36+
}
37+
38+
public static XmlTokenBuffer xmlBufferForInputBuffering(JsonParser p,
39+
ObjectReadContext ctxt) {
40+
return new XmlTokenBuffer(p, ctxt);
41+
}
42+
43+
/*
44+
/**********************************************************************
45+
/* Parser construction overrides
46+
/**********************************************************************
47+
*/
48+
49+
@Override
50+
public JsonParser asParser(ObjectReadContext readCtxt)
51+
{
52+
return _wrapIfNeeded(super.asParser(readCtxt));
53+
}
54+
55+
@Override
56+
public JsonParser asParser(ObjectReadContext readCtxt, JsonParser p0)
57+
{
58+
return _wrapIfNeeded(super.asParser(readCtxt, p0));
59+
}
60+
61+
protected JsonParser _wrapIfNeeded(JsonParser p) {
62+
return (_wrappableParser == null) ? p
63+
: new ElementWrappableParser(p, _wrappableParser);
64+
}
65+
66+
/*
67+
/**********************************************************************
68+
/* Helper classes
69+
/**********************************************************************
70+
*/
71+
72+
/**
73+
* A thin parser delegate that implements {@link ElementWrappable} by
74+
* forwarding wrapping configuration to the original XML parser.
75+
* This allows {@link WrapperHandlingDeserializer} to find and configure
76+
* virtual wrapping even when the active parser is reading from a
77+
* {@link TokenBuffer}.
78+
*/
79+
static class ElementWrappableParser extends JsonParserDelegate
80+
implements ElementWrappable
81+
{
82+
protected final ElementWrappable _wrappable;
83+
84+
ElementWrappableParser(JsonParser delegate, ElementWrappable wrappable) {
85+
super(delegate);
86+
_wrappable = wrappable;
87+
}
88+
89+
@Override
90+
public void addVirtualWrapping(Set<String> namesToWrap, boolean caseInsensitive) {
91+
_wrappable.addVirtualWrapping(namesToWrap, caseInsensitive);
92+
}
93+
}
94+
}

src/test/java/tools/jackson/dataformat/xml/tofix/TypeInfoOrder525Test.java renamed to src/test/java/tools/jackson/dataformat/xml/deser/TypeInfoOrder525Test.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package tools.jackson.dataformat.xml.tofix;
1+
package tools.jackson.dataformat.xml.deser;
22

33
import java.util.List;
44

@@ -9,8 +9,6 @@
99
import tools.jackson.databind.ObjectMapper;
1010

1111
import tools.jackson.dataformat.xml.XmlTestUtil;
12-
import tools.jackson.dataformat.xml.testutil.failure.JacksonTestFailureExpected;
13-
1412
import static org.junit.jupiter.api.Assertions.assertEquals;
1513

1614
// Tests for [dataformat-xml#525], related to relative order of "type"
@@ -49,7 +47,7 @@ static class BindingInfo525 {
4947
.defaultUseWrapper(false)
5048
.build();
5149

52-
@JacksonTestFailureExpected
50+
// [dataformat-xml#525]
5351
@Test
5452
public void testTypeAfterOtherProperties() throws Exception {
5553
String xml =

src/test/java/tools/jackson/dataformat/xml/tofix/PolymorphicList426Test.java renamed to src/test/java/tools/jackson/dataformat/xml/lists/PolymorphicList426Test.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package tools.jackson.dataformat.xml.tofix;
1+
package tools.jackson.dataformat.xml.lists;
22

33
import java.util.List;
44

@@ -13,8 +13,6 @@
1313
import tools.jackson.dataformat.xml.XmlTestUtil;
1414
import tools.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
1515
import tools.jackson.dataformat.xml.annotation.JacksonXmlProperty;
16-
import tools.jackson.dataformat.xml.testutil.failure.JacksonTestFailureExpected;
17-
1816
import static org.junit.jupiter.api.Assertions.assertEquals;
1917
import static org.junit.jupiter.api.Assertions.assertNotNull;
2018

@@ -91,7 +89,6 @@ public JsonTypeInfo.Id getMechanism() {
9189
private final ObjectMapper MAPPER = newMapper();
9290

9391
// [dataformat-xml#426]
94-
@JacksonTestFailureExpected
9592
@Test
9693
public void testPolymorphicList426() throws Exception
9794
{

0 commit comments

Comments
 (0)