Search code examples
javaxmljaxbjaxb2xsi

JAXB - empty tags with no xsi:nil


I have a String property in an object annotated as follows:

@XmlElement(name = "Item", required = true, nillable = true)
private String item;

The result after marshaling is

<Item xsi:nil="true"/>

while I would like it to be

<Item/>

since the third-party service accepting my XML messages wants it like the latter case. I am using jaxb2. Does anyone knows how I could possibly do this?

Thanks a lot


Solution

  • Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.

    The following example requires the use of MOXy as the JAXB provider. This is because the JAXB RI does not call the XmlAdapter when the field/property is null. For information on specifying MOXy as your JAXB provider see:

    StringAdapter

    The XmlAdapter will convert the String value to an object with a property annotated with @XmlValue.

    package forum8986842;
    
    import javax.xml.bind.annotation.*;
    import javax.xml.bind.annotation.adapters.XmlAdapter;
    
    public class StringAdapter extends XmlAdapter<StringAdapter.AdaptedString, String>{
    
        @Override
        public String unmarshal(AdaptedString adaptedString) throws Exception {
            if(null == adaptedString) {
                return null;
            }
            String string = adaptedString.value;
            if("".equals(string)) {
                return null;
            }
            return string;
        }
    
        @Override
        public AdaptedString marshal(String string) throws Exception {
            AdaptedString adaptedString = new AdaptedString();
            adaptedString.value = string;
            return adaptedString;
        }
    
        public static class AdaptedString {
            @XmlValue public String value;
        }
    
    }
    

    Root

    The @XmlJavaTypeAdapter annotation is used to specify the XmlAdapter:

    package forum8986842;
    
    import javax.xml.bind.annotation.*;
    import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
    
    @XmlRootElement(name="Root")
    public class Root {
    
        private String item;
    
        @XmlElement(name = "Item", required = true, nillable = true)
        @XmlJavaTypeAdapter(StringAdapter.class)
        public String getItem() {
            return item;
        }
    
        public void setItem(String item) {
            this.item = item;
        }
    
    }
    

    Demo

    The following code can be used to demonstrate the above mapping. Two documents are used one with an empty Item element, and the other with a populated Item element.

    package forum8986842;
    
    import java.io.StringReader;
    import javax.xml.bind.*;
    
    public class Demo {
    
        private JAXBContext jc;
    
        public Demo() throws JAXBException {
            jc = JAXBContext.newInstance(Root.class);
        }
    
        public static void main(String[] args) throws Exception {
            Demo demo = new Demo();
            demo.demo("<Root><Item/></Root>");
            demo.demo("<Root><Item>Hello World</Item></Root>");
        }
    
        private void demo(String xml) throws JAXBException {
            System.out.println("\n\nINPUT:  " + xml);
            StringReader stringReader = new StringReader(xml);
            Unmarshaller unmarshaller = jc.createUnmarshaller();
            Root root = (Root) unmarshaller.unmarshal(stringReader);
    
            System.out.println("ITEM:   " + root.getItem());
    
            System.out.print("OUTPUT: ");
            Marshaller marshaller = jc.createMarshaller();
            marshaller.setProperty(Marshaller.JAXB_FRAGMENT, true);
            marshaller.marshal(root, System.out);
        }
    
    }
    

    Output

    The following is the output from running the demo code:

    INPUT:  <Root><Item/></Root>
    ITEM:   null
    OUTPUT: <Root><Item/></Root>
    
    INPUT:  <Root><Item>Hello World</Item></Root>
    ITEM:   Hello World
    OUTPUT: <Root><Item>Hello World</Item></Root>
    

    For More Information