I have a nillable field in a class that is being set by the unmarshaller:
@XmlElement(name = "value", nillable = true)
private BigDecimal valueVariable;
My problem is that I can't tell if the xml element has been omitted or set to nil:
A. element <value/>
is missing from the XML file, it is not required.
=> (valueVariable == null) is true
B. XML file contains <value xsi:nil="true"/>
=> (valueVariable == null) is true
How can I tell for a non-String variable if the value is xsi:nil
or the tag is missing?
UPDATE You can see 2 good solutions, I preferred one of them, but the other would also be fine!
JAXB (JSR-222) implementations can represent null
as either an absent node or a nillable element based on the nillable
setting on @XmlElement
. When you need to support both, or differentiate between the two then you can leverage JAXBElement
.
Root
Fields/properties of type JAXBElement
are mapped with the @XmlElementRef
annotation. This corresponds to @XmlElementDecl
annotations on a class annotated with @XmlRegistry
.
import java.math.BigDecimal;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.annotation.*;
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Root {
@XmlElementRef(name="foo")
JAXBElement<BigDecimal> foo;
@XmlElementRef(name="bar")
JAXBElement<BigDecimal> bar;
@XmlElementRef(name="baz")
JAXBElement<BigDecimal> baz;
}
ObjectFactory
import java.math.BigDecimal;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.annotation.*;
import javax.xml.namespace.QName;
@XmlRegistry
public class ObjectFactory {
@XmlElementDecl(name = "foo")
public JAXBElement<BigDecimal> createFoo(BigDecimal value) {
return new JAXBElement<BigDecimal>(new QName("foo"), BigDecimal.class, value);
}
@XmlElementDecl(name = "bar")
public JAXBElement<BigDecimal> createBar(BigDecimal value) {
return new JAXBElement<BigDecimal>(new QName("bar"), BigDecimal.class, value);
}
@XmlElementDecl(name = "baz")
public JAXBElement<BigDecimal> createBaz(BigDecimal value) {
return new JAXBElement<BigDecimal>(new QName("baz"), BigDecimal.class, value);
}
}
input.xml
<?xml version="1.0" encoding="UTF-8"?>
<root>
<bar xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>
<baz>123.456</baz>
</root>
Demo
Below is some demo code you can run to show that everything works. Note how the JAXBIntrospector
can be used to get the real value unwrapping the JAXBElement
if necessary.
import java.io.File;
import java.math.BigDecimal;
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Root.class, ObjectFactory.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
File xml = new File("src/forum18440987/input.xml");
Root root = (Root) unmarshaller.unmarshal(xml);
nullOrAbsent("foo", root.foo);
nullOrAbsent("bar", root.bar);
nullOrAbsent("baz", root.baz);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(root, System.out);
}
private static void nullOrAbsent(String property, JAXBElement<BigDecimal> value) {
System.out.print(property);
if(null == value) {
System.out.print(": ABSENT - ");
} else if(value.isNil()) {
System.out.print(": NIL - ");
} else {
System.out.print(": VALUE - ");
}
System.out.println(JAXBIntrospector.getValue(value));
}
}
Output
foo: ABSENT - null
bar: NIL - null
baz: VALUE - 123.456
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root>
<bar xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>
<baz>123.456</baz>
</root>
UPDATE
If you wanted to maintain your existing get/set methods, then you could keep field access as I have in this answer and change your accessor methods to look like the following:
public BigDecimal getBar() {
if(null == bar) {
return null;
}
return bar.getValue();
}
public void setBar(BigDecimal bar) {
if(null == this.bar) {
this.bar = new JAXBElement<BigDecimal>(new QName("bar"), BigDecimal.class, bar);
} else {
this.bar.setValue(bar);
}
}
Additionally you could add an isSet
method to see if the value had been set.
public boolean isSetBar() {
return null != bar;
}
This approach does not require that you have access to the Unmarshaller
. To make sure that the ObjectFactory
is picked up you can use the @XmlSeeAlso
annotation to reference it from one of your domain classes.
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
@XmlSeeAlso(ObjectFactory.class)
public class Root {