JAXB 2.x : How to override an XmlElement annotation from parent class - Mission Impossible?
Solution 1
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
Solution 2
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>
basZero
WORK: Web App Developer, Java Enthusiast since 1995 HOME: I'm in love with photography
Updated on July 05, 2022Comments
-
basZero almost 2 years
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.
-
jmj over 13 years+1 nice to see you :) , 1 Q. in base class its private then how will it come to child class too ? is it due to overridden setter getters ?
-
basZero over 13 yearsThat's great and really helps!
-
basZero over 13 yearsI will compile another example where not only a String is used: Let's say class A uses Bean with 2 string properties whereas class B uses a Bean with 3 properties. If I do not annotate class B, only the 2 parameters are marshalled instead of 3... let me prepare the example...
-
bdoughan over 13 yearsJigar Joshi - In this example it is the accessors that are annotated. So when populating the object, a JAXB impl will use set methods. These set methods can be different on the parent and child as long as they are named the same.
-
bdoughan over 13 yearsbzero - No problem, if this solves your problem please consider accepting and/or up voting this answer.
-
basZero over 13 yearsNo it is not yet solved, I'll update my question then a better example with incorporating your initial hint.
-
jmj over 13 years
These set methods can be different on the parent and child as long as they are named the same.
You mean object ofMethod
using reflection ? -
basZero over 13 years@Blaise: thanks! Actually I just use Java SE 6, forgot to mention that. I think there is even another JAXB impl in it compared to the RI. I will try out MOXy
-
basZero over 13 yearsHOW TO INSTALL: Is it possible to use only MOXy or do I need to download the whole EclipseLink JARs?
-
bdoughan over 13 years@bzero - Java SE 6 from Oracle (Sun) will contain the JAXB reference implementation Metro. I'm the MOXy tech lead.
-
bdoughan over 13 yearsYou can use MOXy with either of the following configurations: 1) eclipselink.jar 2) Bundles: org.eclipse.persistence.asm, org.eclipse.persistence.core, and org.eclipse.persistence.moxy.
-
basZero over 13 years@Blaise: I see... So Oracle should update the SE6! I'm about to install the MOXy via Update Center in Eclipse... if I put the jaxb.properties into the package folder of my JAXB-beans only, I assume that there will be no impact on other XML binding libs in my application, right?
-
bdoughan over 13 years@bzero - SE6 receives regular updates with fixes to the JAXB RI. You should report this issue as a Metro bug at: java.net/jira/browse/JAXB
-
basZero over 13 years@Blaise: it works now for me too with MOXy: <?xml version="1.0" encoding="UTF-8"?> <root xmlns:xsi="w3.org/2001/XMLSchema-instance" xsi:type="b"> <source> <string1>1</string1> <string2>2</string2> <string3>3</string3> </source> </root>
-
basZero over 13 yearsWhy does it print out the xsi:type ?
-
bdoughan over 13 years@bzero - As far as JAXB is concerned the property is of type DataA, since this is the property type on the super class. When an instance of DataB is marshalled the xsi:type attribute is used to qualify it as an instance of a sub type. Check out: bdoughan.blogspot.com/2010/11/…
-
basZero over 13 yearsLINE NOT WORKING with MOXy: m.setProperty("com.sun.xml.internal.bind.namespacePrefixMapper", mapper);
-
basZero over 13 yearsCan you comment on this: stackoverflow.com/questions/4661263/…
-
bdoughan over 13 yearsMOXy does not support the namespacePrefixMapper, however you can do this much easier in MOXy. MOXy will use the prefixes as specified in the @XmlSchema annotation. For an example see: stackoverflow.com/questions/3289644/…
-
basZero over 13 yearsAmazing, thanks! now it fully works as it should have worked 1 day ago.
-
basZero over 13 yearsHi, that's an interesting approach but did you mix up something? Because there is class A and there is "B extends A", so A does not know anything about B. Applying an annotation in class A which actually references class B is breaking the object oriented data modeling... ?
-
basZero over 13 yearsMaybe you can apply the @Also only to class B? then it would be ok and I'd try it out.
-
basZero almost 8 years@BlaiseDoughan: i'm upgrading from Java 1.6 to Java 1.8 now. Would you still recommend to go with Moxy?