Why is this not possible? It seems so simple but it does not behave as expected.
Summary: Class A uses an aggregated DataA bean whereas Class B (a subclass of Class A) is using an aggregated DataB bean (whereas DataB extends DataA).
I wrote these test classes to visualize and explain my question:
Class A:
package test;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
@XmlAccessorType(XmlAccessType.NONE)
@XmlRootElement(name="root")
public class A {
private DataA source = new DataA();
@XmlElement(name="source")
public DataA getSource() {
return source;
}
public void setSource(DataA source) {
this.source = source;
}
}
and its DataA class (I used the FIELD annotation so that all fields gets marshalled):
package test;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
@XmlAccessorType(XmlAccessType.FIELD)
public class DataA {
public String string1 = "1";
public String string2 = "2";
}
And now the Class B (subclass of Class A): My goal is to reuse functionalities of A and also reuse the properties from the DataA bean by using the DataB bean:
package test;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
@XmlAccessorType(XmlAccessType.NONE)
@XmlRootElement(name="root")
public class B extends A {
private DataB source = new DataB();
public DataB getSource() {
return this.source;
}
public void setSource(DataB source) {
this.source = source;
}
}
Its corresponding DataB bean looks like this:
package test;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
@XmlAccessorType(XmlAccessType.FIELD)
public class DataB extends DataA {
public String string3 = "3";
}
Now, when I marshall an instance of class A, it gives this output:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root>
<source>
<string1>1</string1>
<string2>2</string2>
</source>
</root>
When I marshall an instance of class B, I get the very same result:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root>
<source>
<string1>1</string1>
<string2>2</string2>
</source>
</root>
But I expected that also string3 would get marshalled, but it is only writing the properties of bean DataA! WHY? This is not really intuitive when thinking in terms of OOP.
When I set the @XmlElement annotation also on the Class B... like this:
@XmlElement
public DataB getSource() {
return this.source;
}
... then the property gets marshalled twice because it is once annotated by the parent class as well as by the child class. This is also what I do not want:
The output now is:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root>
<source xsi:type="dataB" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<string1>1</string1>
<string2>2</string2>
<string3>3</string3>
</source>
<source>
<string1>1</string1>
<string2>2</string2>
<string3>3</string3>
</source>
</root>
What I expected from JAXB as a result is the following XML:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root>
<source>
<string1>1</string1>
<string2>2</string2>
<string3>3</string3>
</source>
</root>
Any hints how to tweak JAXB to produce the expected result?? Thanks for any feedback.
Just don't annotate the source property on class B. The source property was mapped on the parent class and should not be mapped again on the child class. Since you are annotating the get/set methods the appropriate get/set will be called on class B.
package test;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
@XmlAccessorType(XmlAccessType.NONE)
@XmlRootElement(name="root")
public class B extends A {
private StringBuffer source = null;
public String getSource() {
return source.toString();
}
public void setSource(String source) {
this.source = new StringBuffer(source);
}
}
UPDATE
There may be a bug in the Metro JAXB (reference implementation). When I run this updated example with EclipseLink JAXB (MOXy) I get the following output:
<?xml version="1.0" encoding="UTF-8"?>
<root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<source>
<string1>1</string1>
<string2>2</string2>
</source>
</root>
<?xml version="1.0" encoding="UTF-8"?>
<root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="b">
<source xsi:type="dataB">
<string1>1</string1>
<string2>2</string2>
<string3>3</string3>
</source>
</root>
This can be reproduced with the following code:
package test;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(A.class, B.class);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
A a = new A();
DataA da = new DataA();
da.string1 = "1";
da.string2 = "2";
a.setSource(da);
marshaller.marshal(a, System.out);
B b = new B();
DataB db = new DataB();
db.string1 = "1";
db.string2 = "2";
db.string3 = "3";
b.setSource(db);
marshaller.marshal(b, System.out);
}
}
To use MOXy as the JAXB implementation you need to supply a file named jaxb.properties in the model package (test) with the following entry:
javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
you don't need to use MOXy.. Just change the Class B and use @XmlAlso.
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "root")
public class B extends A {
}
@XmlAccessorType(XmlAccessType.FIELD)
@XmlSeeAlso({ DataA.class, DataB.class })
@XmlRootElement(name = "source")
@XmlType(name = "source")
public class DataA {
private String string1 = "1";
private String string2 = "2";
.....//getters and setters here
}
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "source")
public class DataB extends DataA {
private String string3 = "3";
.....//getters and setters here
}
@XmlAccessorType(XmlAccessType.FIELD)
@XmlSeeAlso({ A.class, B.class })
@XmlRootElement(name = "root")
public class A {
private DataA source = new DataA();
public DataA getSource() {
return source;
}
public void setSource(DataA source) {
this.source = source;
}
}
B b = new B();
DataB db = new DataB();
db.setString1("1");
db.setString2("2");
db.setString3("3");
b.setSource(db);
marshaller.marshal(b, System.out);
WILL FINALLY WRITE:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root>
<source>
<string1>1</string1>
<string2>2</string2>
</source>
</root>
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root>
<source xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="dataB">
<string1>1</string1>
<string2>2</string2>
<string3>3</string3>
</source>
</root>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With