Vrushank
Vrushank

Reputation: 2813

Marshalled XML has no children when using mixed types

I am trying to unmarshall a DICOM Sequence VR type which is stored in the database as an XML CLOB. As per the standard, the sequence can contain properties as well as sequences as children. An example for reference is the Content Sequence attribute.

As a result, I am using @XmlMixed in my JAXB DTOs to handle the case where the child property may also be a sequence. However, on marshalling the unmarshalled XML, the resultant output is just the root element without the children.

Below is my input XML representing the aforementioned Content Sequence.

<Root>
  <ps>
    <p name="0040A010" vt="8">HAS CONCEPT MOD</p>
    <p name="0040A040" vt="8">CODE</p>
    <p name="0040A043" vt="8">
      <ps>
        <p name="00080100" vt="8">121049</p>
        <p name="00080102" vt="8">DCM</p>
        <p name="00080104" vt="8">Language of Content Item and Descendants
        </p>
      </ps>
    </p>
    <p name="0040A168" vt="8">
      <ps>
        <p name="00080100" vt="8">eng</p>
        <p name="00080102" vt="8">ISO639_2</p>
        <p name="00080104" vt="8">English</p>
      </ps>
    </p>
  </ps>
  <ps>
    <p name="0040A010" vt="8">HAS OBS CONTEXT</p>
    <p name="0040A040" vt="8">CODE</p>
    <p name="0040A043" vt="8">
      <ps>
        <p name="00080100" vt="8">121005</p>
        <p name="00080102" vt="8">DCM</p>
        <p name="00080104" vt="8">Observer Type</p>
      </ps>
    </p>
    <p name="0040A168" vt="8">
      <ps>
        <p name="00080100" vt="8">121006</p>
        <p name="00080102" vt="8">DCM</p>
        <p name="00080104" vt="8">Person</p>
      </ps>
    </p>
  </ps>
  <ps>
    <p name="0040A010" vt="8">HAS OBS CONTEXT</p>
    <p name="0040A040" vt="8">PNAME</p>
    <p name="0040A043" vt="8">
      <ps>
        <p name="00080100" vt="8">121008</p>
        <p name="00080102" vt="8">DCM</p>
        <p name="00080104" vt="8">Person Observer Name</p>
      </ps>
    </p>
    <p name="0040A123" vt="8">IMAGE</p>
  </ps>
  <ps>
    <p name="00081199" vt="8">
      <ps>
        <p name="00081150" vt="8">1.2.840.10008.5.1.4.1.1.4</p>
        <p name="00081155" vt="8">1.3.6.1.4.1.5962.99.1.3923360762.207819601.1541521685498.13.0
        </p>
        <p name="00081199" vt="8">
          <ps>
            <p name="00081150" vt="8">1.2.840.10008.5.1.4.1.1.11.1</p>
            <p name="00081155" vt="8">1.2.840.114356.2019.12.115.113.18.116.1508.6
            </p>
          </ps>
        </p>
        <p name="00750010" vt="8">GEIIS_IW</p>
        <p name="007510A1" vt="8">1</p>
      </ps>
    </p>
    <p name="0040A010" vt="8">CONTAINS</p>
    <p name="0040A040" vt="8">IMAGE</p>
  </ps>
</Root>

And below are the classes used to map bind the above structure:

@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "p")
public class Property
{
   @XmlElementRef(name = "ps", type = PropertySequence.class, required = false)
   @XmlMixed
   protected List<Object> content;

   @XmlAttribute(name = "name", required = true)
   protected String name;

   public List<Object> getContent()
   {
      if (content == null)
      {
         content = new ArrayList<Object>();
      }

      return this.content;
   }

   public String getName()
   {
      return name;
   }

   public void setName(String value)
   {
      this.name = value;
   }
}
@XmlAccessorType(XmlAccessType.FIELD) 
@XmlRootElement(name = "ps") 
public class PropertySequence 
{ 
   protected List<Property> property; 

   public List<Property> getProperty() 
   { 
      if (property == null) 
      { 
         property = new ArrayList<Property>(); 
      } 

      return this.property; 
   } 
}
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "Root")
public class Root
{
   @XmlElement(required = true)
   protected List<PropertySequence> propertySequence;

   public List<PropertySequence> getPropertySequence()
   {
      if (propertySequence == null)
      {
         propertySequence = new ArrayList<PropertySequence>();
      }

      return this.propertySequence;
   }
}

On running the below test code, the output XML after unmarshalling and subsequent marshalling, the output is only the root tag.

try (InputStream xmlStream = Launcher.class.getResourceAsStream("/PropertySequence.xml")) 
{ 
   JAXBContext context = JAXBContext.newInstance(Root.class); 

   Unmarshaller unmarshaller = context.createUnmarshaller(); 
   Root root = (Root) unmarshaller.unmarshal(xmlStream); 

   Marshaller marshaller = context.createMarshaller(); 
   marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); 
   marshaller.marshal(root, System.out);
} 
catch (IOException | JAXBException e) 
{ 
   e.printStackTrace(); 
}

Output

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Root/>

Upvotes: 1

Views: 175

Answers (1)

Michał Ziober
Michał Ziober

Reputation: 38665

XmlMixed & XmlElementRef

Even with XmlElementRef we are not able to deserialise value to PropertySequence or String directly. Anyway, much easier will be by using JAXBElement. Let's see model:

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "Root", propOrder = {"ps"})
public class Root {

    protected List<PropertySequence> ps;
}

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "ps", propOrder = {"p"})
public class PropertySequence {

    protected List<Property> p;

    public List<Property> getP() {
        if (p == null) {
            p = new ArrayList<>();
        }
        return this.p;
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        for (Property property : p) {
            builder.append(property).append(System.lineSeparator());
        }
        return builder.toString();
    }
}

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "p", propOrder = {"content"})
public class Property {

    @XmlMixed
    @XmlElementRef(name = "ps", type = JAXBElement.class, required = false)
    protected List<Serializable> content;

    @XmlAttribute(name = "name")
    protected String name;

    @XmlAttribute(name = "vt")
    protected String vt;

    @XmlTransient
    public String getStringValue() {
        if (content != null && content.size() == 1) {
            return content.get(0).toString();
        }

        return null;
    }

    @XmlTransient
    public PropertySequence getPropertySequence() {
        if (content != null && content.size() == 3) {
            return ((JAXBElement<PropertySequence>) content.get(1)).getValue();
        }

        return null;
    }

    public List<Serializable> getContent() {
        if (content == null) {
            content = new ArrayList<>();
        }
        return this.content;
    }

    public String getName() {
        return name;
    }

    public void setName(String value) {
        this.name = value;
    }

    public String getVt() {
        return vt;
    }

    public void setVt(String value) {
        this.vt = value;
    }

    @Override
    public String toString() {
        Object value = getStringValue();
        if (value == null) {
            value = getPropertySequence();
        }
        return "Property{" +
                "content=" + value +
                ", name='" + name + '\'' +
                ", vt='" + vt + '\'' +
                '}';
    }
}

@XmlRegistry
public class ObjectFactory {

    private final static QName ROOT_QNAME = new QName("", "Root");
    private final static QName PropertySequence_QNAME = new QName("", "ps");

    @XmlElementDecl(namespace = "", name = "Root")
    public JAXBElement<Root> createRoot(Root value) {
        return new JAXBElement<>(ROOT_QNAME, Root.class, null, value);
    }

    @XmlElementDecl(namespace = "", name = "ps", scope = Property.class)
    public JAXBElement<PropertySequence> createPropertySequence(PropertySequence value) {
        return new JAXBElement<>(PropertySequence_QNAME, PropertySequence.class, Property.class, value);
    }
}

Simple example:

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import java.io.File;

public class JaxbApp {

    public static void main(String[] args) throws Exception {
        File xmlFile = new File("./resource/test.xml").getAbsoluteFile();

        JAXBContext context = JAXBContext.newInstance(Root.class);

        Unmarshaller unmarshaller = context.createUnmarshaller();
        JAXBElement<Root> root = (JAXBElement<Root>) unmarshaller.unmarshal(xmlFile);
        System.out.println(root.getValue());

        Marshaller marshaller = context.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
        marshaller.marshal(root, System.out);
    }
}

Prints model:

RootType{ps=[Property{content=HAS CONCEPT MOD, name='0040A010', vt='8'}
Property{content=CODE, name='0040A040', vt='8'}
Property{content=Property{content=121049, name='00080100', vt='8'}
Property{content=DCM, name='00080102', vt='8'}
Property{content=Language of Content Item and Descendants, name='00080104', vt='8'}
, name='0040A043', vt='8'}
Property{content=Property{content=eng, name='00080100', vt='8'}
Property{content=ISO639_2, name='00080102', vt='8'}
Property{content=English, name='00080104', vt='8'}
, name='0040A168', vt='8'}
, Property{content=HAS OBS CONTEXT, name='0040A010', vt='8'}
Property{content=CODE, name='0040A040', vt='8'}
Property{content=Property{content=121005, name='00080100', vt='8'}
Property{content=DCM, name='00080102', vt='8'}
Property{content=Observer Type, name='00080104', vt='8'}
, name='0040A043', vt='8'}
Property{content=Property{content=121006, name='00080100', vt='8'}
Property{content=DCM, name='00080102', vt='8'}
Property{content=Person, name='00080104', vt='8'}
, name='0040A168', vt='8'}
, Property{content=HAS OBS CONTEXT, name='0040A010', vt='8'}
Property{content=PNAME, name='0040A040', vt='8'}
Property{content=Property{content=121008, name='00080100', vt='8'}
Property{content=DCM, name='00080102', vt='8'}
Property{content=Person Observer Name, name='00080104', vt='8'}
, name='0040A043', vt='8'}
Property{content=IMAGE, name='0040A123', vt='8'}
, Property{content=Property{content=1.2.840.10008.5.1.4.1.1.4, name='00081150', vt='8'}
Property{content=1.3.6.1.4.1.5962.99.1.3923360762.207819601.1541521685498.13.0
                , name='00081155', vt='8'}
Property{content=Property{content=1.2.840.10008.5.1.4.1.1.11.1, name='00081150', vt='8'}
Property{content=1.2.840.114356.2019.12.115.113.18.116.1508.6
                        , name='00081155', vt='8'}
, name='00081199', vt='8'}
Property{content=GEIIS_IW, name='00750010', vt='8'}
Property{content=1, name='007510A1', vt='8'}
, name='00081199', vt='8'}
Property{content=CONTAINS, name='0040A010', vt='8'}
Property{content=IMAGE, name='0040A040', vt='8'}
]}

XmlMixed & XmlAnyElement

It prints empty root because deserialising does not work properly for your mapping and root object is empty (propertySequence is null). You need to update POJO mapping. Instead fo @XmlElementRef annotation use @XmlAnyElement. Model after few changes could look like below:

@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "Root")
class Root {

    @XmlElement(required = true, name = "ps")
    protected List<PropertySequence> propertySequence;

    // getters, setters, toString
}

@XmlAccessorType(XmlAccessType.FIELD)
class PropertySequence {

    @XmlElement(name = "p")
    protected List<Property> property;

    // getters, setters, toString
}


@XmlAccessorType(XmlAccessType.FIELD)
class Property {

    @XmlAttribute(name = "name", required = true)
    protected String name;

    @XmlAttribute(name = "vt", required = true)
    protected Integer vt;

    @XmlMixed
    @XmlAnyElement
    protected List<Object> value;

    // getters, setters, toString
}

And below code:

import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAnyElement;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlMixed;
import javax.xml.bind.annotation.XmlRootElement;
import java.io.File;
import java.util.ArrayList;
import java.util.List;

public class JaxbApp {

    public static void main(String[] args) throws Exception {
        File xmlFile = new File("./resource/test.xml").getAbsoluteFile();

        JAXBContext context = JAXBContext.newInstance(Root.class);

        Unmarshaller unmarshaller = context.createUnmarshaller();
        Root root = (Root) unmarshaller.unmarshal(xmlFile);
        System.out.println(root);

        Marshaller marshaller = context.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
        marshaller.marshal(root, System.out);
    }
}

prints object:

Root{propertySequence=[PropertySequence{property=[Property{name='0040A010', vt=8, value=[HAS CONCEPT MOD]}, Property{name='0040A040', vt=8, value=[CODE]}, Property{name='0040A043', vt=8, value=[
            , [ps: null], 
        ]}, Property{name='0040A168', vt=8, value=[
            , [ps: null], 
        ]}]}, PropertySequence{property=[Property{name='0040A010', vt=8, value=[HAS OBS CONTEXT]}, Property{name='0040A040', vt=8, value=[CODE]}, Property{name='0040A043', vt=8, value=[
            , [ps: null], 
        ]}, Property{name='0040A168', vt=8, value=[
            , [ps: null], 
        ]}]}, PropertySequence{property=[Property{name='0040A010', vt=8, value=[HAS OBS CONTEXT]}, Property{name='0040A040', vt=8, value=[PNAME]}, Property{name='0040A043', vt=8, value=[
            , [ps: null], 
        ]}, Property{name='0040A123', vt=8, value=[IMAGE]}]}, PropertySequence{property=[Property{name='00081199', vt=8, value=[
            , [ps: null], 
        ]}, Property{name='0040A010', vt=8, value=[CONTAINS]}, Property{name='0040A040', vt=8, value=[IMAGE]}]}]}

and XML:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Root>
    <ps>
        <p name="0040A010" vt="8">HAS CONCEPT MOD</p>
        <p name="0040A040" vt="8">CODE</p>
        <p name="0040A043" vt="8">
            <ps>
                <p name="00080100" vt="8">121049</p>
                <p name="00080102" vt="8">DCM</p>
                <p name="00080104" vt="8">Language of Content Item and Descendants</p>
            </ps>
        </p>
        <p name="0040A168" vt="8">
            <ps>
                <p name="00080100" vt="8">eng</p>
                <p name="00080102" vt="8">ISO639_2</p>
                <p name="00080104" vt="8">English</p>
            </ps>
        </p>
    </ps>
    <ps>
        <p name="0040A010" vt="8">HAS OBS CONTEXT</p>
        <p name="0040A040" vt="8">CODE</p>
        <p name="0040A043" vt="8">
            <ps>
                <p name="00080100" vt="8">121005</p>
                <p name="00080102" vt="8">DCM</p>
                <p name="00080104" vt="8">Observer Type</p>
            </ps>
        </p>
        <p name="0040A168" vt="8">
            <ps>
                <p name="00080100" vt="8">121006</p>
                <p name="00080102" vt="8">DCM</p>
                <p name="00080104" vt="8">Person</p>
            </ps>
        </p>
    </ps>
    <ps>
        <p name="0040A010" vt="8">HAS OBS CONTEXT</p>
        <p name="0040A040" vt="8">PNAME</p>
        <p name="0040A043" vt="8">
            <ps>
                <p name="00080100" vt="8">121008</p>
                <p name="00080102" vt="8">DCM</p>
                <p name="00080104" vt="8">Person Observer Name</p>
            </ps>
        </p>
        <p name="0040A123" vt="8">IMAGE</p>
    </ps>
    <ps>
        <p name="00081199" vt="8">
            <ps>
                <p name="00081150" vt="8">1.2.840.10008.5.1.4.1.1.4</p>
                <p name="00081155" vt="8">1.3.6.1.4.1.5962.99.1.3923360762.207819601.1541521685498.13.0
                </p>
                <p name="00081199" vt="8">
                    <ps>
                        <p name="00081150" vt="8">1.2.840.10008.5.1.4.1.1.11.1</p>
                        <p name="00081155" vt="8">1.2.840.114356.2019.12.115.113.18.116.1508.6
                        </p>
                    </ps>
                </p>
                <p name="00750010" vt="8">GEIIS_IW</p>
                <p name="007510A1" vt="8">1</p>
            </ps>
        </p>
        <p name="0040A010" vt="8">CONTAINS</p>
        <p name="0040A040" vt="8">IMAGE</p>
    </ps>
</Root>

Upvotes: 1

Related Questions