Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JAXB How to marshal an element both optional or nillable

Tags:

I'm trying to marshal an element which can be null and in some cases should be not written in the final XML at all, in some other cases I should specify that it is nillable.

Consider the following example

<root>
    <element>
        <sub1>Whatever1</sub1>
        <sub2 xsi:nil="true"/>
    </element>
    <element>
        <sub1>Whatever2</sub1>
        <sub2>Not empty</sub2>
    </element>
    <element>
        <sub1>Whatever3</sub1>
    </element>
</root>

The element "sub2" is the one I'm having trouble with.

From my understanding I can easily achieve the first 2 cases with the Element class declared as follows:

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "element", propOrder = {
        "sub1",
        "sub2",
})
public class Element {

    @XmlElement(required = true)
    public String sub1;
    @XmlElement(nillable = true)
    public String sub2;
    //...
}

To obtain the last two instead, I would do:

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "element", propOrder = {
        "sub1",
        "sub2",
})
public class Element {

    @XmlElement(required = true)
    public String sub1;
    @XmlElement(required = false) //I could omit it entirely
    public String sub2;
    //...
}

Is there a way to obtain both?

I'm forced to Java 6 / JAXB 2.1.10.

Thank you.

like image 542
Nevril Avatar asked Oct 31 '17 17:10

Nevril


1 Answers

Yes, it is possible to have elements which are both not required and nillable. There are a few things you need to do to make this work:

  1. Make the field a JAXBElement<String> instead of String.
  2. Create an ObjectFactory class with a factory method.
  3. Put @XmlElementDecl on the factory method in the ObjectFactory and @XmlElementRef on the field in class Element.

Example:

Make the field a JAXBElement<String> with an @XmlElementRef annotation:

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "element", propOrder = {"sub1", "sub2"})
@XmlRootElement
public class Element {

    @XmlElement(required = true)
    public String sub1;

    @XmlElementRef(name = "sub2", required = false)
    public JAXBElement<String> sub2;

    // ...
}

Create an ObjectFactory class with a factory method, with an @XmlElementDecl annotation. Note that the namespace and name of the @XmlElementRef and @XmlElementDecl annotation are the same:

@XmlRegistry
public class ObjectFactory {

    @XmlElementDecl(name = "sub2")
    public JAXBElement<String> createSub2(String value) {
        return new JAXBElement<>(new QName(null, "sub2"),
                                 String.class, Element.class, value);
    }
}

Marshalling example 1: Have a value for sub2:

ObjectFactory objectFactory = new ObjectFactory();

Element element = new Element();
element.setSub1("one");
element.setSub2(objectFactory.createSub2("two"));

JAXBContext context = JAXBContext.newInstance(ObjectFactory.class, Element.class);

Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(element, System.out);

Output:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<element>
    <sub1>one</sub1>
    <sub2>two</sub2>
</element>

Marshalling example 2: No element at all in the XML:

Element element = new Element();
element.setSub1("one");
// Leave the field sub2 set to null

Output:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<element>
    <sub1>one</sub1>
</element>

Marshalling example 3: Create an element with the value null which will appear as an XML element which is set to nil:

Element element = new Element();
element.setSub1("one");
element.setSub2(objectFactory.createSub2(null));

Output:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<element>
    <sub1>one</sub1>
    <sub2 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>
</element>
like image 179
Jesper Avatar answered Sep 19 '22 13:09

Jesper