Skip to content

Commit ac00d64

Browse files
authored
Fixes #634: add deserialization support for xsi:type auto-detection (#635)
1 parent 5063969 commit ac00d64

File tree

4 files changed

+92
-1
lines changed

4 files changed

+92
-1
lines changed

release-notes/VERSION-2.x

+2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ Project: jackson-dataformat-xml
1414
(reported by Arthur C)
1515
#631: Add `XmlMapper.createGenerator(XMLStreamWriter)` and
1616
`XmlMapper.createParser(XMLStreamReader)` overloads
17+
#634: Support use of xsi:type for polymorphic deserialization
18+
(FromXmlParser.Feature.AUTO_DETECT_XSI_TYPE)
1719
* Upgrade Woodstox to 6.6.0 (latest at the time)
1820

1921
2.16.1 (24-Dec-2023)

src/main/java/com/fasterxml/jackson/dataformat/xml/deser/FromXmlParser.java

+15
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import java.math.BigInteger;
88
import java.util.Set;
99

10+
import javax.xml.XMLConstants;
1011
import javax.xml.stream.XMLStreamException;
1112
import javax.xml.stream.XMLStreamReader;
1213
import javax.xml.stream.XMLStreamWriter;
@@ -57,6 +58,20 @@ public class FromXmlParser
5758
*/
5859
public enum Feature implements FormatFeature
5960
{
61+
/**
62+
* Feature that enables automatic conversion of incoming "xsi:type"
63+
* (where "type" is the local name and "xsi" prefix is bound to URI
64+
* {@link XMLConstants#W3C_XML_SCHEMA_INSTANCE_NS_URI}),
65+
* into Jackson simple Property Name of {@code "xsi:type"}.
66+
* This is usually needed to read content written using
67+
* matching {@code ToXmlGenerator.Feature#AUTO_DETECT_XSI_TYPE} feature,
68+
* usually used for Polymorphic handling where it is difficult
69+
* to specify proper XML Namespace for type identifier.
70+
*
71+
* @since 2.17
72+
*/
73+
AUTO_DETECT_XSI_TYPE(false),
74+
6075
/**
6176
* Feature that indicates whether XML Empty elements (ones where there are
6277
* no separate start and end tags, but just one tag that ends with "/>")

src/main/java/com/fasterxml/jackson/dataformat/xml/deser/XmlTokenStream.java

+22-1
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@ public class XmlTokenStream
7575

7676
protected boolean _cfgProcessXsiNil;
7777

78+
// @since 2.17
79+
protected boolean _cfgProcessXsiType;
80+
7881
protected XmlNameProcessor _nameProcessor;
7982

8083
/*
@@ -169,6 +172,7 @@ public XmlTokenStream(XMLStreamReader xmlReader, ContentReference sourceRef,
169172
_sourceReference = sourceRef;
170173
_formatFeatures = formatFeatures;
171174
_cfgProcessXsiNil = FromXmlParser.Feature.PROCESS_XSI_NIL.enabledIn(_formatFeatures);
175+
_cfgProcessXsiType = FromXmlParser.Feature.AUTO_DETECT_XSI_TYPE.enabledIn(_formatFeatures);
172176
// 04-Dec-2023, tatu: [dataformat-xml#618] Need further customized adapter:
173177
_xmlReader = Stax2JacksonReaderAdapter.wrapIfNecessary(xmlReader);
174178
_nameProcessor = nameProcessor;
@@ -247,6 +251,7 @@ public XMLStreamReader2 getXmlReader() {
247251
protected void setFormatFeatures(int f) {
248252
_formatFeatures = f;
249253
_cfgProcessXsiNil = FromXmlParser.Feature.PROCESS_XSI_NIL.enabledIn(f);
254+
_cfgProcessXsiType = FromXmlParser.Feature.AUTO_DETECT_XSI_TYPE.enabledIn(f);
250255
}
251256

252257
/*
@@ -517,7 +522,6 @@ private final int _next() throws XMLStreamException
517522
return (_currentState = XML_END);
518523
case XML_END:
519524
return XML_END;
520-
// throw new IllegalStateException("No more XML tokens available (end of input)");
521525
}
522526
// Ok: must be END_ELEMENT; see what tag we get (or end)
523527
switch (_skipAndCollectTextUntilTag()) {
@@ -712,6 +716,15 @@ private final void _checkXsiAttributes() {
712716
* @since 2.14
713717
*/
714718
protected void _decodeElementName(String namespaceURI, String localName) {
719+
// 31-Jan-2024, tatu: [dataformat-xml#634] Need to convert 'xsi:type'?
720+
// (not 100% sure if needed for elements but let's do for now)
721+
if (_cfgProcessXsiType) {
722+
if (localName.equals("type") && XSI_NAMESPACE.equals(namespaceURI)) {
723+
_localName = "xsi:type";
724+
_namespaceURI = ""; // or could leave as it was?
725+
return;
726+
}
727+
}
715728
_nameToDecode.namespace = namespaceURI;
716729
_nameToDecode.localPart = localName;
717730
_nameProcessor.decodeName(_nameToDecode);
@@ -723,6 +736,14 @@ protected void _decodeElementName(String namespaceURI, String localName) {
723736
* @since 2.14
724737
*/
725738
protected void _decodeAttributeName(String namespaceURI, String localName) {
739+
// 31-Jan-2024, tatu: [dataformat-xml#634] Need to convert 'xsi:type'?
740+
if (_cfgProcessXsiType) {
741+
if (localName.equals("type") && XSI_NAMESPACE.equals(namespaceURI)) {
742+
_localName = "xsi:type";
743+
_namespaceURI = ""; // or could leave as it was?
744+
return;
745+
}
746+
}
726747
_nameToDecode.namespace = namespaceURI;
727748
_nameToDecode.localPart = localName;
728749
_nameProcessor.decodeName(_nameToDecode);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package com.fasterxml.jackson.dataformat.xml.deser;
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
import com.fasterxml.jackson.annotation.JsonRootName;
5+
import com.fasterxml.jackson.annotation.JsonTypeInfo;
6+
import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
7+
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
8+
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
9+
import com.fasterxml.jackson.dataformat.xml.XmlTestBase;
10+
import com.fasterxml.jackson.dataformat.xml.ser.ToXmlGenerator;
11+
12+
// [dataformat-xml#634]
13+
public class XsiTypeReadTest extends XmlTestBase
14+
{
15+
@JsonRootName("Typed")
16+
static class TypeBean {
17+
@JsonProperty("xsi:type")
18+
public String typeId;
19+
20+
protected TypeBean() { }
21+
public TypeBean(String typeId) {
22+
this.typeId = typeId;
23+
}
24+
}
25+
26+
@JsonRootName("Poly")
27+
@JsonTypeInfo(use = Id.SIMPLE_NAME, include = As.PROPERTY, property="xsi:type")
28+
static class PolyBean {
29+
public int value;
30+
31+
protected PolyBean() { }
32+
public PolyBean(int v) { value = v; }
33+
}
34+
35+
private final XmlMapper XSI_ENABLED_MAPPER = XmlMapper.builder()
36+
.configure(ToXmlGenerator.Feature.AUTO_DETECT_XSI_TYPE, true)
37+
.configure(FromXmlParser.Feature.AUTO_DETECT_XSI_TYPE, true)
38+
.build();
39+
40+
public void testExplicitXsiTypeReadEnabled() throws Exception
41+
{
42+
final String XML = XSI_ENABLED_MAPPER.writeValueAsString(new TypeBean("type0"));
43+
TypeBean result = XSI_ENABLED_MAPPER.readValue(XML, TypeBean.class);
44+
assertEquals("type0", result.typeId);
45+
}
46+
47+
public void testXsiTypeAsTypeReadeEnabled() throws Exception
48+
{
49+
final String XML = XSI_ENABLED_MAPPER.writeValueAsString(new PolyBean(42));
50+
PolyBean result = XSI_ENABLED_MAPPER.readValue(XML, PolyBean.class);
51+
assertEquals(42, result.value);
52+
}
53+
}

0 commit comments

Comments
 (0)