JAXB inheritance, unmarshal to subclass of marshaled class

You're using JAXB 2.0 right? (since JDK6)

There is a class:

javax.xml.bind.annotation.adapters.XmlAdapter<ValueType,BoundType>

which one can subclass, and override following methods:

public abstract BoundType unmarshal(ValueType v) throws Exception;
public abstract ValueType marshal(BoundType v) throws Exception;

Example:

public class YourNiceAdapter
        extends XmlAdapter<ReceiverPerson,Person>{

    @Override public Person unmarshal(ReceiverPerson v){
        return v;
    }
    @Override public ReceiverPerson marshal(Person v){
        return new ReceiverPerson(v); // you must provide such c-tor
    }
}

Usage is done by as following:

@Your_favorite_JAXB_Annotations_Go_Here
class SomeClass{
    @XmlJavaTypeAdapter(YourNiceAdapter.class)
    Person hello; // field to unmarshal
}

I'm pretty sure, by using this concept you can control the marshalling/unmarshalling process by yourself (including the choice the correct [sub|super]type to construct).


Subclass Person twice, once for receiver and once for sender, and only put the XmlRootElement on these subclassses (leaving the superclass, Person, without an XmlRootElement). Note that sender and receiver both share the same JAXB base classes.

@XmlRootElement(name="person")
public class ReceiverPerson extends Person {
  // receiver specific code
}

@XmlRootElement(name="person")
public class SenderPerson extends Person {
  // sender specific code (if any)
}

// note: no @XmlRootElement here
public class Person {
  // data model + jaxb annotations here
}

[tested and confirmed to work with JAXB]. It circumvents the problem you note, when multiple classes in the inheritance hierarchy have the XmlRootElement annotation.

This is arguably also a neater and more OO approach, because it separates out the common data model, so it's not a "workaround" at all.


The following snippet is a method of a Junit 4 test with a green light:

@Test
public void testUnmarshallFromParentToChild() throws JAXBException {
  Person person = new Person();
  int age = 30;
  String name = "Foo";
  person.name = name;
  person.age= age;

  // Marshalling
  JAXBContext context = JAXBContext.newInstance(person.getClass());
  Marshaller marshaller = context.createMarshaller();

  StringWriter writer = new StringWriter();
  marshaller.marshal(person, writer);

  String outString = writer.toString();

  assertTrue(outString.contains("</person"));

  // Unmarshalling
  context = JAXBContext.newInstance(Person.class, RecieverPerson.class);
  Unmarshaller unmarshaller = context.createUnmarshaller();
  StringReader reader = new StringReader(outString);
  RecieverPerson reciever = (RecieverPerson)unmarshaller.unmarshal(reader);

  assertEquals(name, reciever.name);
  assertEquals(age, reciever.age);
}

The important part is the use of the JAXBContext.newInstance(Class... classesToBeBound) method for the unmarshalling context:

 context = JAXBContext.newInstance(Person.class, RecieverPerson.class);

With this call, JAXB will compute a reference closure on the classes specified and will recognize RecieverPerson. The test passes. And if you change the parameters order, you'll get a java.lang.ClassCastException (so they must be passed in this order).


Create a custom ObjectFactory to instantiate the desired class during unmarshalling. Example:

JAXBContext context = JAXBContext.newInstance("com.whatever.mypackage");
Unmarshaller unmarshaller = context.createUnmarshaller();
unmarshaller.setProperty("com.sun.xml.internal.bind.ObjectFactory", new ReceiverPersonObjectFactory());
return unmarshaller;

public class ReceiverPersonObjectFactory extends ObjectFactory {
    public Person createPerson() {
        return new ReceiverPerson();
    }
}

Tags:

Java

Xml

Jaxb