Search code examples
javajaxbmarshallingoption-typeunmarshalling

Java JAXB marshall/unmarshall using Java Optionals


My applications needs to convert data between Java and XML.

When converting the data, I need to distinguish whether or not the value was present, the value was set explicitly to null or the value had a value.

XML example:

<person><name>Bob</name></person>     <-- element 'name' contains value "Bob"

<person><name nil="true"/></person>   <-- element 'name' was set explicitly to 'nil'/null

<person></person>                     <-- element 'name' is missing

As Java types like 'String' only knows two states (null or not null), I tried to use Java Optionals to solve this.

A mapping between XML and Java Optionals could look like this:

<person></person>                   <=> Optional<String> name = null;

<person><name>Bob</name></person>   <=> Optional<String> name = Optional.of("Bob");

<person><name nil="true"/></person> <=> Optional<String> name = Optional.empty();

I tried to use JAXB for the marshalling and unmarshalling. The idea was that the setter of a field only gets invoked when a value needs to be set explicitly to an value. That means that a value is absent implicitly.


I had a look on other stackoverflow questions like the following, but all of them were incomplete handling the behaviour I need to achieve:

How to generate JaxB-Classes with java.util.Optional?

Using generic @XmlJavaTypeAdapter to unmarshal wrapped in Guava's Optional

Using Guava's Optional with @XmlAttribute

I've been struggling with this problem for two days now. I tried to use the XMLAdapter and GenericAdapter, tried several ways how to annotate the fields and getter/setter with @XmlElement, tried to use @XmlAnyElment with and without lax, but all of them only led to a partial success. Either the nil value was not handeld correctly, the lists were not printed out correctly, ...

I think every Java webservice with a properly implemented patch operation should have had this problem. (not talking about the "json patch approach" (RFC 6902))

Is there a common way to solve my problem?


Solution

  • Since I was not able to solve the problem completely by solely using and configuring JAXB properly, I decided to solve it as follows:

    (The main goal was to write a subsystem to communicate with an external system based on XML)

    As a starting point, I used the XSD schema provided by the target system to communicate with and generated the corresponding (XML)Java classes using JAXB and the XSD file. All the fields in those generated classes were of type JAXBElement<>, in order to be able to hold the 3 states needed (absent, null, someValue).

    On the business model side, I used Java classes with Optional<> field types in order to hold the 3 states.

    For the mapping, I wrote a mapper which uses reflection to recursively map from JAXB to Java and vice versa. When mapping from Java to JAXB, the mapper used the ObjectFactory to create the JAXBElement objects. (Mapper itself just had about 300 lines of code). The fields were mapped based on the matching field names.

    The most ugly and challenging part was, that the XSD schema file needed to be altered, in order to make JAXB generated classes that uses JAXBElement field types. Therefore I had to manually add the attribute minOccurs="0" nillable="true" to the XML elements, if not already set.

    With that solution above, I finally managed to map the XML to Java and vice versa considering the 3 states needed, easily.

    Of course, this solution has its drawbacks. One is the manual modification of the XSD file. Usually bad practice to alter the XSD file provided by the external system, which acts as an interface contract.

    For my requirements at the time, the solution worked perfectly. Even changes to the interface contract of the external system could be implemented very easily.