jackson-dataformat-xml

repository·3.x·Indexed 20 days ago

https://github.com/fasterxml/jackson-dataformat-xml

A Jackson data format module for XML data binding that enables serialization and deserialization of Java objects to and from XML using the standard Jackson API. It provides the XmlMapper class for data binding, support for incremental reading and writing via Stax, and specialized annotations such as @JacksonXmlProperty and @JacksonXmlRootElement. Compatible versions are available for both Jackson 2.x and 3.x.

Tokens
2.8K
Snippets
10
Records
13
Agent score
21%

What's inside jackson-dataformat-xml

  1. Install jackson-dataformat-xml for Jackson 2.x

    3.x

    To use the Jackson 2.x compatible version of this extension, add the following dependency to your project.

    Maven:

    <dependency>
      <groupId>com.fasterxml.jackson.dataformat</groupId>
      <artifactId>jackson-dataformat-xml</artifactId>
      <version>2.21.2</version>
    </dependency>

    Gradle:

    dependencies {
        implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.21.2'
    }
    <dependency>
      <groupId>com.fasterxml.jackson.dataformat</groupId>
      <artifactId>jackson-dataformat-xml</artifactId>
      <version>2.21.2</version>
    </dependency>
  2. Perform incremental XML reading and writing

    3.x

    You can perform incremental/partial processing by combining Stax components (XMLInputFactory, XMLStreamWriter, XMLStreamReader) with XmlMapper.

    Incremental Writing:

    XMLInputFactory xmlInputFactory = XMLInputFactory.newFactory();
    XMLOutputFactory xmlOutputFactory = XMLOutputFactory.newFactory();
    StringWriter out = new StringWriter();
    XMLStreamWriter sw = xmlOutputFactory.createXMLStreamWriter(out);
    
    XmlMapper mapper = new XmlMapper(xmlInputFactory);
    
    sw.writeStartDocument();
    sw.writeStartElement("root");
    
    // Write POJOs incrementally
    mapper.writeValue(sw, somePojoInstance);
    
    sw.writeEndElement();
    sw.writeEndDocument();

    Incremental Reading:

    XMLInputFactory f = XMLInputFactory.newFactory();
    XMLStreamReader sr = f.createXMLStreamReader(new FileInputStream(inputFile));
    
    XmlMapper mapper = new XmlMapper();
    
    sr.next(); // Move to <root>
    sr.next(); // Move to first child element
    
    // Read a single POJO from the current stream position
    SomePojo value1 = mapper.readValue(sr, SomePojo.class);
    
    // Move forward to the next element
    sr.next(); 
    SomePojo value2 = mapper.readValue(sr, SomePojo.class);
    
    sr.close();
  3. Add Woodstox for better XML performance

    3.x

    It is recommended to include Woodstox as your XML library. It is faster than the JDK's default Stax implementation and handles namespace prefixes more effectively.

    Maven:

    <dependency>
      <groupId>com.fasterxml.woodstox</groupId>
      <artifactId>woodstox-core</artifactId>
      <version>6.5.0</version>
    </dependency>

    Gradle:

    dependencies {
        implementation 'com.fasterxml.woodstox:woodstox-core:6.5.0'
    }
    <dependency>
      <groupId>com.fasterxml.woodstox</groupId>
      <artifactId>woodstox-core</artifactId>
      <version>6.5.0</version>
    </dependency>
  4. Install jackson-dataformat-xml for Jackson 3.x

    3.x

    To use the Jackson 3.x compatible version of this extension, add the following dependency to your project.

    Maven:

    <dependency>
      <groupId>tools.jackson.dataformat</groupId>
      <artifactId>jackson-dataformat-xml</artifactId>
      <version>3.1.1</version>
    </dependency>

    Gradle:

    dependencies {
        implementation 'tools.jackson.dataformat:jackson-dataformat-xml:3.1.1'
    }
    <dependency>
      <groupId>tools.jackson.dataformat</groupId>
      <artifactId>jackson-dataformat-xml</artifactId>
      <version>3.1.1</version>
    </dependency>
  5. Configure XML list wrapping behavior

    3.x

    By default, Jackson annotations treat lists and arrays as "wrapped" elements. If you need to change this behavior to match JAXB-style "unwrapped" elements, you have two options:

    1. Per-field control: Use the @JacksonXmlElementWrapper.useWrapping annotation on a specific field and set it to false.
    2. Global control: Use JacksonXmlModule.setDefaultUseWrapper() to set the default behavior for the entire module.
    // Example of disabling wrapping for a specific field
    @JacksonXmlElementWrapper(useWrapping = false)
    public List<String> myItems;
  6. Deserialize POJOs from XML

    3.x

    Deserialization is performed using XmlMapper.readValue().

    XmlMapper xmlMapper = new XmlMapper();
    Simple value = xmlMapper.readValue("<Simple><x>1</x><y>2</y></Simple>", Simple.class);
    Simple value = xmlMapper.readValue("<Simple><x>1</x><y>2</y></Simple>", Simple.class);
  7. Serialize POJOs to XML

    3.x

    Serialization is performed using XmlMapper. It works similarly to JSON serialization.

    // Create the mapper
    XmlMapper xmlMapper = new XmlMapper();
    
    // Serialize to String
    String xml = xmlMapper.writeValueAsString(new Simple());
    
    // Serialize to File
    xmlMapper.writeValue(new File("/tmp/stuff.xml"), new Simple());

    Example POJO:

    public class Simple {
        public int x = 1;
        public int y = 2;
    }

    Output:

    <Simple>
      <x>1</x>
      <y>2</y>
    </Simple>
    XmlMapper xmlMapper = new XmlMapper();
    String xml = xmlMapper.writeValueAsString(new Simple());
  8. Instantiate XmlMapper

    3.x

    Most usage of this module is through the data-binding level using XmlMapper.

    Simple instantiation:

    XmlMapper mapper = new XmlMapper();

    Configurable instantiation (Builder pattern): For Jackson 2.10+, use the XmlMapper.builder() to configure settings like defaultUseWrapper or enable/disable features.

    XmlMapper mapper = XmlMapper.builder()
       .defaultUseWrapper(false)
       .build();
    XmlMapper mapper = new XmlMapper();
  9. Configure XmlMapper with custom Stax factories

    3.x

    If you need to control low-level XML processing details (e.g., via Woodstox), you can construct an XmlMapper with a custom XmlFactory configured with specific XMLInputFactory and XMLOutputFactory implementations.

    XMLInputFactory ifactory = new WstxInputFactory(); // Woodstox implementation
    ifactory.setProperty(WstxInputProperties.P_MAX_ATTRIBUTE_SIZE, 32000);
    
    XMLOutputFactory ofactory = new WstxOutputFactory(); // Woodstox implementation
    ofactory.setProperty(WstxOutputProperties.P_OUTPUT_CDATA_AS_TEXT, true);
    
    XmlFactory xf = XmlFactory.builder()
        .xmlInputFactory(ifactory)
        .xmlOutputFactory(ofactory)
        .build();
    
    XmlMapper mapper = new XmlMapper(xf);
    XmlMapper mapper = new XmlMapper(xf);
  10. Known limitations of jackson-dataformat-xml

    3.x

    When using jackson-dataformat-xml, be aware of the following limitations that differ from standard Jackson (JSON) behavior:

    Model & Content Limitations

    • Streaming Model: Direct usage of the streaming model is not officially supported; it is intended to be used via databinding.
    • Tree Model (JsonNode): The tree model is based on the JSON content model and does not perfectly match the XML infoset:
      • Mixed Content: Elements containing both text and child elements are not supported; text content will be lost.
      • Repeated Elements: Prior to version 2.12, handling of repeated elements was problematic (only the last element was retained). This is improved in later versions.
    • Mixed Content in Databinding: Child content must be either text OR elements (attributes are acceptable).

    Serialization & Deserialization Limitations

    • Root Values: The root value should ideally be a POJO. While support for other types (Primitives, Strings, Enums, Arrays, Collections) has improved over time, they may not always behave as intended as root values.
    • Namespaces: While namespaces are recognized and produced during serialization, namespace URIs are NOT verified during deserialization. Only local names are matched, meaning elements differing only by namespace cannot be distinguished.
    • Root Name Wrapping: Support for SerializationFeature.WRAP_ROOT_VALUE and DeserializationFeature.UNWRAP_ROOT_VALUE is incomplete:
      • Serialization: Does NOT add wrapping (as of version 2.13).
      • Deserialization: DOES unwrap the root element (fixed in 2.13.0 after a temporary removal in 2.12.x).

    Annotations & Polymorphism

    • List/Array Wrapping: By default, Jackson annotations wrap lists/arrays, whereas JAXB annotations unwrap them. You can control this via @JacksonXmlElementWrapper.useWrapping or JacksonXmlModule.setDefaultUseWrapper().
    • Polymorphic Type Handling: Only certain inclusion mechanisms are supported. For example, WRAPPER_ARRAY is not supported due to XML mapping complexities. JAXB-style "compact" Type Id (where the property name is replaced by the Type Id) is also not supported.
  11. Use Jackson XML annotations

    3.x

    In addition to standard Jackson and JAXB annotations, this module provides specific annotations for XML-specific requirements:

    • @JacksonXmlElementWrapper: Specifies the XML element used to wrap List and Map properties.
    • @JacksonXmlProperty: Specifies the XML namespace and local name for a property, and whether it should be written as an XML element or an attribute.
    • @JacksonXmlRootElement: Specifies the XML element used for the root element (defaults to the class's simple name).
    • @JacksonXmlText: Specifies that a property's value should be serialized as unwrapped text rather than in an element.
    • @JacksonXmlCData: Specifies that a property's value should be serialized within a CDATA tag.
  12. Implement a custom XmlNameProcessor to handle invalid XML characters

    3.x

    The XmlNameProcessor interface allows you to customize how XML element and attribute names are processed, which is useful for handling names containing invalid XML characters (e.g., characters appearing in Map keys).

    To use a custom processor, implement the XmlNameProcessor interface and register it using the XmlFactoryBuilder#xmlNameProcessor method.

    Your implementation must provide two methods:

    1. encodeName(XmlName name): Used during serialization to escape or encode invalid characters in the provided XmlName.
    2. decodeName(XmlName name): Used during deserialization to revert the encoding applied by encodeName. Note that 100% accuracy in reversing encoding is not always required or possible.

    The XmlName class provides the namespace and localPart of the XML element or attribute.

    public class MyCustomProcessor implements XmlNameProcessor {
        @Override
        public void encodeName(XmlName name) {
            // Logic to escape invalid characters in name.localPart
        }
    
        @Override
        public void decodeName(XmlName name) {
            // Logic to revert encoding in name.localPart
        }
    }
    
    // Registration via XmlFactoryBuilder
    XmlFactory factory = new XmlFactoryBuilder()
        .xmlNameProcessor(new MyCustomProcessor())
        .build();