How to create an abstract marshal class JAXB Marshaller based on its runtime type?

Consider the following abstract class -

public abstract class Car { public abstract void drive(double miles); } 

Here's an example class (for illustrative purposes) that extends the class above.

 public class Ferrari extends Car { private String lastUsed; // Ferrari specific field not in Car private boolean f1Car; // Ferrari specific field not in Car @XmlElement public void setF1Car(boolean f1Car) { this.f1Car = f1Car; } public boolean isF1Car() { return f1Car; } @XmlElement public void setLastUsed(String lastUsed) { this.lastUsed = lastUsed; } public String getLastUsed() { return lastUsed; } public void drive(double miles) { // implementation } } 

I have a report class containing a Car object -

 @XmlRootElement public class CarTestReport { private String date; private double miles; private Car car; @XmlElement public void setDate(String date) { this.date = date;} public String getDate() {return date;} @XmlElement public void setMiles(double miles) { this.miles = miles; } public double getMiles() {return miles;} @XmlElement public void setCar(Car car) { this.car = car; } public Car getCar() { return car; } } 

And here is the piece of code using JAXB for the Marshall CarTestReport object -

 public static void main(String[] args) throws Exception { Ferrari ferrari = new Ferrari(); ferrari.setLastUsed("July 5 2012"); ferrari.setF1Car(false); CarTestReport report = new CarTestReport(); report.setDate("July 6 2012"); report.setMiles(200); report.setCar(ferrari); File file = new File("carTestReport.xml"); JAXBContext jaxbContext = JAXBContext.newInstance(CarTestReport.class); Marshaller jaxbMarshaller = jaxbContext.createMarshaller(); jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); jaxbMarshaller.marshal(report, file); } 

The problem is that due to the abstract type Car JAXB ignores it and does not march the Ferrari object when it marshals the CarTestReport object. The output I get is

 <carTestReport> <car/> <date>July 6 2012</date> <miles>200.0</miles> </carTestReport> 

As you can see, nothing was entered under the β€œcar” node, although the Ferrari object was populated. How to solve this problem?

+7
source share
2 answers

The JAXB system does not look up the classpath for any possible JAXB-annotated classes. You must help him find them. In your code example, it simply does not know about the existence of the Ferrari class. (He sees only Car , because the recipient type is returned in CarTestReport .)

One quick and dirty way to tell JAXB about Ferrari is to add @XmlSeeAlso({Ferrari.class}) to the top of your Car class. Then you will get the result as follows:

 <carTestReport> <car xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="ferrari"> <f1Car>false</f1Car> <lastUsed>July 5 2012</lastUsed> </car> <date>July 6 2012</date> <miles>200.0</miles> </carTestReport> 

Another way to tell JAXB about Ferrari is to pass this class to the JAXBContext.newInstance method, i.e.:

 JAXBContext jaxbContext = JAXBContext.newInstance(CarTestReport.class, Ferrari.class); 

Or if all of your JAXB classes are in the same package, for example. com.mycompany.carstuff , you can do this:

 JAXBContext jaxbContext = JAXBContext.newInstance("com.mycompany.carstuff"); 

And in this latter case, it will search for all classes in this package.

If you want it to produce an element named Ferrari (instead of <car xsi:type="ferrari"> as mentioned above), one possibility is to add @XmlType to the top of your Car class, for example:

 import javax.xml.bind.annotation.XmlSeeAlso; import javax.xml.bind.annotation.XmlType; @XmlSeeAlso({Ferrari.class}) @XmlType public abstract class Car { public abstract void drive(double miles); } 

... and put @XmlRootElement on a Ferrari , for example:

 import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement public class Ferrari extends Car { // ... } 

From what I understand, this combination of annotations tells JAXB that the Car class is mapped to an XML schema type (so you won't get any elements named "car") and that the Ferrari class is an element of this type (so you can have elements named "ferrari"). And the "root" in @XmlRootElement is misleading ... it can be an element anywhere in the structure of your objects.

+16
source

You need to annotate your Ferrari class using @XmlRootElement and replace @XmlElement with the car attribute @XmlElementRef

+1
source

All Articles