简体   繁体   中英

JAXB fails to marshal null value when using XMLAdapter

I use Guava Optional fields in my source code and I wanted to marshal an object that uses Optional to xml, so I used JAXB for that.

I created a XMLAdapter for the same:

public abstract class OptionalAdpater<T> extends XmlAdapter<T, Optional<T>> {

    @Override
    public Optional<T> unmarshal(T v) throws Exception {
        return Optional.of(v);
    }

    @Override
    public T marshal(Optional<T> v) throws Exception {
        if (v == null) { 
            return null;
        }

        return v.isPresent() ? v.get() : null;
    }
}

But when I use this in my class

@XmlRootElement(name = "test")
public class Test {

    private Optional<Integer> optionalValue = Optional.absent();

    @XmlElement(name = "optional-value", type = Integer.class)
    @XmlJavaTypeAdapter(type = Integer.class, value = OptionalIntegerAdapter.class)
    public Optional<Integer> getOptionalValue() {
        return optionalValue;
    }

    public void setOptionalValue(Optional<Integer> optionalValue) {
        this.optionalValue = optionalValue;
    }
}

JAXB fails with a NullPointerException . What I don't get is, if I don't use an optional type and just use boxed Integer and the value is null then JAXB skips marshalling that field, but doesn't work when I use an adapter with optional type.

Trace:

Exception in thread "main" java.lang.NullPointerException
    at com.sun.xml.internal.bind.v2.model.impl.RuntimeBuiltinLeafInfoImpl$17.print(RuntimeBuiltinLeafInfoImpl.java:717)
    at com.sun.xml.internal.bind.v2.model.impl.RuntimeBuiltinLeafInfoImpl$17.print(RuntimeBuiltinLeafInfoImpl.java:711)
    at com.sun.xml.internal.bind.v2.model.impl.RuntimeBuiltinLeafInfoImpl$StringImpl.writeLeafElement(RuntimeBuiltinLeafInfoImpl.java:149)
    at com.sun.xml.internal.bind.v2.runtime.reflect.TransducedAccessor$CompositeTransducedAccessorImpl.writeLeafElement(TransducedAccessor.java:241)
    at com.sun.xml.internal.bind.v2.runtime.property.SingleElementLeafProperty.serializeBody(SingleElementLeafProperty.java:114)
    at com.sun.xml.internal.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:341)
    at com.sun.xml.internal.bind.v2.runtime.XMLSerializer.childAsSoleContent(XMLSerializer.java:582)
    at com.sun.xml.internal.bind.v2.runtime.ClassBeanInfoImpl.serializeRoot(ClassBeanInfoImpl.java:323)
    at com.sun.xml.internal.bind.v2.runtime.XMLSerializer.childAsRoot(XMLSerializer.java:483)
    at com.sun.xml.internal.bind.v2.runtime.MarshallerImpl.write(MarshallerImpl.java:308)
    at com.sun.xml.internal.bind.v2.runtime.MarshallerImpl.marshal(MarshallerImpl.java:236)
    at javax.xml.bind.helpers.AbstractMarshallerImpl.marshal(AbstractMarshallerImpl.java:103)

This is the code to blame:

        primaryList.add(new StringImpl<Integer>(Integer.class,
            createXS("int"),
            createXS("unsignedShort")
            ) {
            public Integer parse(CharSequence text) {
                return DatatypeConverterImpl._parseInt(text);
            }

            public String print(Integer v) {
                return DatatypeConverterImpl._printInt(v);
            }
        });

_printInt accepts int so if you return a null integer, you have an implicit unboxing.

Why it works without adapter/optional is probably becasue of the optional. There's probably some logic somewhere which checks properties for null . You don't have null , you have an Optional .

From my point of view, this is a bug in JAXB. The runtime should check for null after adapter has converted them, not before.

How to resolve it - I think the only way at the moment is to adapt your Optional<Integer> to String instead of Integer .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM