Marshalling derived classes with JAXB

When I have to convert Java objects to XML strings, I normally use JAXB, the nice standard library included in the Java Standard Edition since version 1.6. I have already written in the past about marshaling and unmarshaling (that means, transform an object to an XML stream, and vice-versa), but I have recently used JAXB to marshal a class that has a polymorphic data member. There are a few differences from the standard behavior that I think they deserve some extra words.

I have a hierarchy of classes, that I want to use polimorphically in another class. Something like this:
@XmlRootElement // 1
public class Data {
  @XmlElement
  private String a;

  // ...
}

@XmlRootElement 
public class DataExt extends Data {
  @XmlElement
  private String b;

  // ...
}

@XmlRootElement
public class Phone {
  @XmlElement
  private String type;
  @XmlElement // 2
  private Data data;

  // ...
}
1. This is not enough. When JAXB performs the binding, it doesn't care about the runtime class associated to the current object, but it goes straight to the compile time type. We should explicitly say which other classes are part of the hierarchy, using the XmlSeeAlso annotation.
2. We need also to tell to JAXB that it should check the runtime type for this object. This is done by replacing the XmlElement annotation with @XmlElementRef. Actually, at least in my case, this is not a strict necessity. Once specified @XmlSeeAlso on the class definition, JAXB is smart enough to deduce that it has to polimorphically treat the related objects. Still, it adds an attribute to element, in my case xsi:type="dataExt", to show which is the actual type it is using.

So, we'd better change an annotation, and add another one:
@XmlRootElement
public class Phone {
  @XmlElement
  private String type;
  @XmlElementRef
  private Data data;

  // ...
}

@XmlRootElement
@XmlSeeAlso(DataExt.class)
public class Data {
  @XmlElement
  private String a;

  // ...
}
Besides, I also wanted to keep transparent to the XML user which kind of actual class I used. Meaning that I want the same tag for elements referring to the Data hierarchy. That's very easy. I specify the same name for both XmlRootElement's:
@XmlRootElement(name="data")
public class DataExt extends Data {
  @XmlElement
  private String b;

  // ...
}

No comments:

Post a Comment