java unmarshalling - NULL value or missing tag?
Asked Answered
T

3

8

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!

Tillandsia answered 26/8, 2013 at 9:33 Comment(0)
C
6

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.

Java Model

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);
    }

}

Demo Code

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 {
Citronellal answered 26/8, 2013 at 10:14 Comment(4)
Thanks, that's what I needed. But my definitions are like @XmlElement(name = "value", nillable = true) private JAXBElement<BigDecimal> valueVariable;, I don't use @XmlElementRef. I get the error: No default constructor found on class javax.xml.bind.JAXBElement java.lang.NoSuchMethodException: javax.xml.bind.JAXBElement.<init>()Tillandsia
And BTW it's a web service and unmarshalling is not controlled by me.Tillandsia
Oh, now I see, just to change the Type of the private variable in the @XmlElement from BigDecimal to JAXBElement<BigDecimal> is not a solution. I gotta have the ObjectFactory too. So, maybe it will be easier to generate the java code from an xsd...Tillandsia
@Tillandsia - I have updated my answer with some additional information you may find useful.Citronellal
S
1

There's a similar question here: Handling missing nodes with JAXB. It seems that if the element is missing, the setter method won't be called. So you could put some logic in the method to determine if the node is missing or has an empty value.

@XmlElement(name = "value", nillable = true)
private BigDecimal valueVariable;

private boolean valueVariableMissing = true;

public void setValueVariable(BigDecimal valueVariable){
    this.valueVariable = valueVariable;
    this.valueVariableMissing = false;
}
Scholastic answered 26/8, 2013 at 9:40 Comment(0)
T
0

I was dealing with the same issue, In order to fix it i created a new file in the package were I was marshaling the object:
"package-info.java" There I added:

@javax.xml.bind.annotation.XmlSchema(namespace = "http://the.correct.namespace.com/gateway/", elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED)
package com.the.package.name;

Now everything is ok

Tidewaiter answered 14/1, 2014 at 13:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.