Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can you change the default value of the nillable attribute in JAXB?

Tags:

java

jaxb

I am refactoring some code to use JAXB and reflection to output code to the client, it is currently using an XMLWriter and manually creating the tags each time.

The problem I am having is that due to constraints on the client side, I need to have empty elements in the XML for any null fields in the java class.

While I realize this problem can be solved by adding nillable=true to each JAXB XmlElement annotation, that is not the most practical, as I have a lot of those annotations.

I was hoping to find a way to set nillable=true as a global attribute (or as the default value). This would also make it easier for future colleagues to work on it, as they won't need to remember that every annotation should include the nillable attribute.

I haven't found much besides descriptions of the default behavior. I find it surprising that no one else has posted a similar question in the past. From what I have found, it doesn't seem to me that there is any built-in support for making the default configurable. Is this something that might be solved with a custom JAXB implementation or maybe a third party JAXB implementation?

like image 842
jgibson Avatar asked Jan 12 '12 19:01

jgibson


1 Answers

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

I have entered an enhancement request to have this behaviour added to EclipseLink JAXB (MOXy):

  • http://bugs.eclipse.org/368547

WORK AROUND

As a work around if all your mapped String fields/properties are mapped to XML elements then the following XmlAdapter approach may work for you:

NullStringAdapter

This XmlAdapter will marshal instances of String as an object called AdaptedString. AdaptedString contains the String value as well as a field mapped to the xsi:nil attribute. In the XmlAdapter we will set the value of that field based on whether or not the String value is null.

package forum8841221;

import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.*;

public class NullStringAdapter extends XmlAdapter<NullStringAdapter.AdaptedString, String> {

    @Override
    public AdaptedString marshal(String v) throws Exception {
        AdaptedString adaptedString = new AdaptedString();
        if(null == v) {
            adaptedString.nil = true;
        }
        adaptedString.value = v;
        return adaptedString;
    }

    @Override
    public String unmarshal(AdaptedString v) throws Exception {
        return v.value;
    }

    public static class AdaptedString {

        @XmlAttribute(namespace="http://www.w3.org/2001/XMLSchema-instance")
        public Boolean nil;

        @XmlValue
        @XmlJavaTypeAdapter(VoidStringAdapter.class)
        public String value;

    }

    public static class VoidStringAdapter extends XmlAdapter<String, String> {

        @Override
        public String marshal(String v) throws Exception {
            return v;
        }

        @Override
        public String unmarshal(String v) throws Exception {
            return v;
        }

    }

}

package-info

We can register that we want this XmlAdapter to apply to all the mapped String fields/properties on this package by registering the XmlAdapter at the package level.

@XmlJavaTypeAdapter(value=NullStringAdapter.class, type=String.class)
@XmlSchema(xmlns={@XmlNs(namespaceURI = "http://www.w3.org/2001/XMLSchema-instance", prefix = "xsi")})
package forum8841221;

import javax.xml.bind.annotation.adapters.*;
import javax.xml.bind.annotation.*;

Root

Below is the domain class I have used for this example. It has several String properties, one of them is annotated with @XmlElement(nillable=true)

package forum8841221;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class Root {

    private String a;
    private String b;
    private String c;
    private String d;

    public String getA() {
        return a;
    }

    public void setA(String a) {
        this.a = a;
    }

    public String getB() {
        return b;
    }

    public void setB(String b) {
        this.b = b;
    }

    public String getC() {
        return c;
    }

    public void setC(String c) {
        this.c = c;
    }

    @XmlElement(nillable=true)
    public String getD() {
        return d;
    }

    public void setD(String d) {
        this.d = d;
    }

}

Demo

package forum8841221;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;

public class Demo {

    public static void main(String[] args) throws Exception {
        JAXBContext jc = JAXBContext.newInstance(Root.class);

        Root root = new Root();
        root.setB("B");

        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.marshal(root, System.out);
    }

}

Output

<?xml version="1.0" encoding="UTF-8"?>
<root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
   <a xsi:nil="true"/>
   <b>B</b>
   <c xsi:nil="true"/>
   <d xsi:nil="true"/>
</root>
like image 193
bdoughan Avatar answered Oct 28 '22 11:10

bdoughan