Any way to marshal @XmlElement List<String> fieldName to <fieldName xsi:nil="true".../> instead of an absent node?

StackOverflow https://stackoverflow.com/questions/21528449

  •  06-10-2022
  •  | 
  •  

Question

I'm trying to get MOXy or JAXB RI to marshal null collections to an xsi:nil element like so:

<element xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>

The use case is:

  • Axis2 SOAP endpoint I have no control of
  • I'm on the client side
  • Endpoint expects xsi:nil elements for null collections but does not allow them to be wrapped (using @XmlElementWrapper) which would generate the required xsi:nil element for the wrapping element.
  • I can only use JAX-WS RI as of JDK 6 r4+ or the latest Metro implementation.
  • I can use the JAXB RI or the MOXy implementation.
  • I'm on EclipseLink 2.5.1
  • The bound types are generated from the service WSDL by wsimport at build time. So any solution not requiring post-processing the generated sources for annotation modification would be preferable.

I'm aware that JAXB RI as of 2.2.6 probably has no way of doing that, but maybe I missed something. I checked the sources and it basically says, non-parametrized: "If it's a List and if it's null, only ever output anything if @XmlElementWrapper is present on field". But unfortunately this is not a solution for this case.

I tried MOXy and the use of its @XmlNullPolicy annotation but it seems to have no effect on collection properties. Is there another way to make MOXy output xsi:nil elements for emtpty collections?

I created a test case to give an example of what happens. First and second are the actual result and the result I need. Third is the jaxb.properties file required to enable MOXy for the actual use case in the fourth code block.

This is what I get when executing the test case at the bottom:

<?xml version="1.0" encoding="UTF-8"?>
<model>
   <nonCollection xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>
   <nonCollectionEmptyNode/>
   <wrappedNullCollection xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>
</model>

This is what I need:

<?xml version="1.0" encoding="UTF-8"?>
<model>
   <nonCollection xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>
   <nonCollectionEmptyNode/>
   <nullCollection xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>
   <wrappedNullCollection xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>
</model>

jaxb.properties This file need to be in the same package as the test case below otherwise MOXy will not be used.

javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory

Test case:

package test;

import java.util.List;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement;
import org.eclipse.persistence.oxm.annotations.XmlMarshalNullRepresentation;
import org.eclipse.persistence.oxm.annotations.XmlNullPolicy;

/**
 * jaxb.properties containing string:
 * "javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory"
 * needs to be in the same package as this class to enable MOXy.
 */
public class XmlNullPolicyTest
{

  @XmlAccessorType(XmlAccessType.FIELD)
  @XmlRootElement
  private static class Model
  {

    /**
     * Outputs xsi:nil elements when using JAXB RI or MOXy.
     */
    @XmlElement(required = true, nillable = true)
    protected String nonCollection;


    /**
     * This is here to test if the EclipseLink implementation is actually used.
     * To enable MOXy JAXB impl put jaxb.properties containing text
     * "javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory"
     * in the same package as this class.
     *
     * Using the RI it outputs:
     * <nonCollectionEmptyNode xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>
     *
     * And with MOXy, because it processes @XmlNullPolicy:
     * <nonCollectionEmptyNode/>
     */
    @XmlElement(required = true, nillable = true)
    @XmlNullPolicy(
      nullRepresentationForXml = XmlMarshalNullRepresentation.EMPTY_NODE
    )
    protected String nonCollectionEmptyNode;

    /**
     * Need this one to work. Should marshal to:
     * <nullCollection xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>
     * ...if possible.
     */
    @XmlElement(required = true, nillable = true)
    @XmlNullPolicy(
      nullRepresentationForXml = XmlMarshalNullRepresentation.XSI_NIL,
      xsiNilRepresentsNull = true
    )
    private List<String> nullCollection = null;

    /**
     * Cannot use this one, since it the service expects elements unwrapped.
     * Works though as long as the list is actually null.
     */
    @XmlElement(required = true, nillable = true)
    @XmlElementWrapper(nillable = true)
    private List<String> wrappedNullCollection = null;
  }

  public static void main(String[] args)
    throws JAXBException
  {

    final JAXBContext jaxbContext =
      JAXBContext.newInstance(Model.class);

    final Marshaller marshaller =
      jaxbContext.createMarshaller();

    marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);

    marshaller.marshal(new Model(), System.out);

  }
}

I appreciate any help.

Thank you for you attention!

Best regards, Christian

Was it helpful?

Solution

Why its Hard to Map

Having the following represent that a collection property is null.

<fieldName xsi:nil=“true”…/> 

When the following would represent a populated collection:

<fieldName>foo</fieldName>
<fieldName>bar</fieldName>

Is a bit odd, since <fieldName xsi:nil=“true”…/> would be interpreted as a collection containing one null entry.

As you mention normally with JAXB you would leverage an @XmlElementWrapper and have the wrapper element indicate that the collection is null.

What You Can Do

I would tweak your data to get the XML representation that you want. You can do this by leveraging before and after marshal events. If the before event convert any null values into a List of size 1 that contained a null value. Then in the after unmarshal event set the List of size that contains a null value back to null.

This can be done with a Marshal.Listener:

Or in callback methods on the class:

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top