diff --git a/src/main/java/com/fasterxml/jackson/dataformat/xml/XmlTypeResolverBuilder.java b/src/main/java/com/fasterxml/jackson/dataformat/xml/XmlTypeResolverBuilder.java
index a90f0697d..a89749dc8 100644
--- a/src/main/java/com/fasterxml/jackson/dataformat/xml/XmlTypeResolverBuilder.java
+++ b/src/main/java/com/fasterxml/jackson/dataformat/xml/XmlTypeResolverBuilder.java
@@ -34,7 +34,6 @@ public XmlTypeResolverBuilder(JsonTypeInfo.Value settings) {
@Override
public StdTypeResolverBuilder init(JsonTypeInfo.Id idType, TypeIdResolver idRes)
{
-
super.init(idType, idRes);
if (_typeProperty != null) {
_typeProperty = StaxUtil.sanitizeXmlTypeName(_typeProperty);
@@ -138,6 +137,8 @@ protected static String decodeXmlClassName(String className)
protected static class XmlClassNameIdResolver
extends ClassNameIdResolver
{
+ private static final long serialVersionUID = 1L;
+
public XmlClassNameIdResolver(JavaType baseType, TypeFactory typeFactory,
PolymorphicTypeValidator ptv)
{
@@ -159,6 +160,8 @@ public JavaType typeFromId(DatabindContext context, String id) throws IOExceptio
protected static class XmlMinimalClassNameIdResolver
extends MinimalClassNameIdResolver
{
+ private static final long serialVersionUID = 1L;
+
public XmlMinimalClassNameIdResolver(JavaType baseType, TypeFactory typeFactory,
PolymorphicTypeValidator ptv)
{
diff --git a/src/main/java/com/fasterxml/jackson/dataformat/xml/ser/ToXmlGenerator.java b/src/main/java/com/fasterxml/jackson/dataformat/xml/ser/ToXmlGenerator.java
index 735a171f4..f8361422a 100644
--- a/src/main/java/com/fasterxml/jackson/dataformat/xml/ser/ToXmlGenerator.java
+++ b/src/main/java/com/fasterxml/jackson/dataformat/xml/ser/ToXmlGenerator.java
@@ -93,6 +93,19 @@ public enum Feature implements FormatFeature
* @since 2.13
*/
UNWRAP_ROOT_OBJECT_NODE(false),
+
+ /**
+ * Feature that enables automatic conversion of logical property
+ * name {@code "xsi:type"} into matching XML name where "type"
+ * is the local name and "xsi" prefix is bound to URI
+ * {@link XMLConstants#W3C_XML_SCHEMA_INSTANCE_NS_URI},
+ * and output is indicated to be done as XML Attribute.
+ * This is mostly desirable for Polymorphic handling where it is difficult
+ * to specify XML Namespace for type identifier
+ *
+ * @since 2.17
+ */
+ AUTO_DETECT_XSI_TYPE(false),
;
final boolean _defaultState;
@@ -247,20 +260,26 @@ public void initGenerator() throws IOException
}
_initialized = true;
try {
+ boolean xmlDeclWritten;
if (Feature.WRITE_XML_1_1.enabledIn(_formatFeatures)) {
_xmlWriter.writeStartDocument("UTF-8", "1.1");
+ xmlDeclWritten = true;
} else if (Feature.WRITE_XML_DECLARATION.enabledIn(_formatFeatures)) {
_xmlWriter.writeStartDocument("UTF-8", "1.0");
+ xmlDeclWritten = true;
} else {
- return;
+ xmlDeclWritten = false;
}
// as per [dataformat-xml#172], try adding indentation
- if (_xmlPrettyPrinter != null) {
+ if (xmlDeclWritten && (_xmlPrettyPrinter != null)) {
// ... but only if it is likely to succeed:
if (!_stax2Emulation) {
_xmlPrettyPrinter.writePrologLinefeed(_xmlWriter);
}
}
+ if (Feature.AUTO_DETECT_XSI_TYPE.enabledIn(_formatFeatures)) {
+ _xmlWriter.setPrefix("xsi", XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI);
+ }
} catch (XMLStreamException e) {
StaxUtil.throwAsGenerationException(e, this);
}
@@ -487,10 +506,12 @@ public void writeRepeatedFieldName() throws IOException
/* JsonGenerator method overrides
/**********************************************************
*/
-
- /* Most overrides in this section are just to make methods final,
- * to allow better inlining...
- */
+
+ @Override
+ public void writeFieldName(SerializableString name) throws IOException
+ {
+ writeFieldName(name.getValue());
+ }
@Override
public final void writeFieldName(String name) throws IOException
@@ -498,12 +519,22 @@ public final void writeFieldName(String name) throws IOException
if (_writeContext.writeFieldName(name) == JsonWriteContext.STATUS_EXPECT_VALUE) {
_reportError("Can not write a field name, expecting a value");
}
- // Should this ever get called?
- String ns = (_nextName == null) ? "" : _nextName.getNamespaceURI();
- _nameToEncode.namespace = ns;
- _nameToEncode.localPart = name;
- _nameProcessor.encodeName(_nameToEncode);
- setNextName(new QName(_nameToEncode.namespace, _nameToEncode.localPart));
+
+ String ns;
+ // 30-Jan-2024, tatu: Surprise!
+ if (Feature.AUTO_DETECT_XSI_TYPE.enabledIn(_formatFeatures)
+ && "xsi:type".equals(name)) {
+ setNextName(new QName(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI,
+ "type", "xsi"));
+ setNextIsAttribute(true);
+ } else {
+ // Should this ever get called?
+ ns = (_nextName == null) ? "" : _nextName.getNamespaceURI();
+ _nameToEncode.namespace = ns;
+ _nameToEncode.localPart = name;
+ _nameProcessor.encodeName(_nameToEncode);
+ setNextName(new QName(_nameToEncode.namespace, _nameToEncode.localPart));
+ }
}
@Override
@@ -519,7 +550,7 @@ public final void writeStringField(String fieldName, String value) throws IOExce
// handling...
//
// See [dataformat-xml#4] for more context.
-
+
/*
// @since 2.9
public WritableTypeId writeTypePrefix(WritableTypeId typeIdDef) throws IOException
@@ -640,12 +671,6 @@ public final void _handleEndObject() throws IOException
/**********************************************************
*/
- @Override
- public void writeFieldName(SerializableString name) throws IOException
- {
- writeFieldName(name.getValue());
- }
-
@Override
public void writeString(String text) throws IOException
{
diff --git a/src/test/java/com/fasterxml/jackson/dataformat/xml/ser/XsiTypeWriteTest.java b/src/test/java/com/fasterxml/jackson/dataformat/xml/ser/XsiTypeWriteTest.java
new file mode 100644
index 000000000..19a9382a5
--- /dev/null
+++ b/src/test/java/com/fasterxml/jackson/dataformat/xml/ser/XsiTypeWriteTest.java
@@ -0,0 +1,60 @@
+package com.fasterxml.jackson.dataformat.xml.ser;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonRootName;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
+import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
+import com.fasterxml.jackson.dataformat.xml.XmlMapper;
+import com.fasterxml.jackson.dataformat.xml.XmlTestBase;
+
+// [dataformat-xml#324]
+public class XsiTypeWriteTest extends XmlTestBase
+{
+ @JsonRootName("Typed")
+ static class TypeBean {
+ @JsonProperty("xsi:type")
+ public String typeId = "abc";
+ }
+
+ @JsonRootName("Poly")
+ @JsonTypeInfo(use = Id.SIMPLE_NAME, include = As.PROPERTY, property="xsi:type")
+ static class PolyBean {
+ public int value = 42;
+ }
+
+ private final XmlMapper NO_XSI_MAPPER = XmlMapper.builder()
+ .configure(ToXmlGenerator.Feature.AUTO_DETECT_XSI_TYPE, false)
+ .build();
+
+ private final XmlMapper XSI_ENABLED_MAPPER = XmlMapper.builder()
+ .configure(ToXmlGenerator.Feature.AUTO_DETECT_XSI_TYPE, true)
+ .build();
+
+ public void testExplicitXsiTypeWriteDisabled() throws Exception
+ {
+ assertEquals("abc",
+ NO_XSI_MAPPER.writeValueAsString(new TypeBean()));
+ }
+
+ public void testExplicitXsiTypeWriteEnabled() throws Exception
+ {
+ assertEquals(
+ a2q(""),
+ a2q(XSI_ENABLED_MAPPER.writeValueAsString(new TypeBean())));
+ }
+
+ public void testXsiTypeAsTypeIdWriteDisabled() throws Exception
+ {
+ assertEquals("abc42",
+ NO_XSI_MAPPER.writeValueAsString(new PolyBean()));
+ }
+
+ public void testXsiTypeAsTypeIdWriteEnabled() throws Exception
+ {
+ assertEquals(
+ a2q(""
+ +"42"),
+ a2q(XSI_ENABLED_MAPPER.writeValueAsString(new PolyBean())));
+ }
+}