Reputation: 161
I want to marshal / unmarshal objects of a class that inherits form another one.
I start with the class Thing
:
import java.util.List;
public class Thing {
private List<String> strings;
public List<String> getStrings() {
return strings;
}
public void setStrings(List<String> strings) {
this.strings = strings;
}
}
I extend this class and annotate it with JAXB-annotations.
import java.util.List;
import javax.xml.bind.annotation.*;
@XmlRootElement
public class JaxbThing extends Thing {
// @XmlElementWrapper(name = "list")
@XmlElementWrapper(name = "strings")
@XmlElement(name = "string")
public List<String> getStrings() {
return super.getStrings();
}
public void setStrings(List<String> string) {
super.setStrings(string);
}
}
Then I run the following marshalling/unmarshalling program:
import java.io.File;
import java.util.Arrays;
import javax.xml.bind.*;
public class Main {
public static void main(String[] args) {
JaxbThing t = new JaxbThing();
t.setStrings(Arrays.asList("a", "b", "c"));
try {
File f = new File("jaxb-file.xml");
JAXBContext context = JAXBContext.newInstance(JaxbThing.class);
Marshaller m = context.createMarshaller();
m.marshal(t, f);
Unmarshaller um = context.createUnmarshaller();
JaxbThing t2 = (JaxbThing) um.unmarshal(f);
System.out.println(t2.getStrings()); // I expect to see [a, b, c]
} catch (JAXBException e) {
e.printStackTrace();
}
}
}
The content of the XML file is:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<jaxbThing>
<strings>
<string>a</string>
<string>b</string>
<string>c</string>
</strings>
</jaxbThing>
Everything seems to be correct. But the result of the unmarshaling is surprising to me, because the console shows:
[
]
When I expected to see [a, b, c]
If I annotate the strings
property in this way:
@XmlElementWrapper(name = "list")
// @XmlElementWrapper(name = "strings")
@XmlElement(name = "string")
public List<String> getStrings() {
return super.getStrings();
}
Then the console shows the expected [a, b, c]
.
I guess that JAXB unmarshaler is using class Thing
instead of JaxbThing
to unmarshal the XML file content. In fact if I annotate the class Thing
with @XmlTransient
I get the expected result.
However I do not understand this behavior of JAXB.
Can anyone explain me it, please? Thank you in advance.
Upvotes: 4
Views: 1343
Reputation: 161
Thank you very much for your answer. I have done what you suggest me. You are right, setStrings
method is called twice. But, at least in my experiment, it is never called with the correct values.
Bellow I provide the modified sample code. I added a few lines to trace execution. I have also activated the marshaller formatted output, in order to better see what happens in the process of unmarshalling.
The base class Thing
import java.util.List;
public class Thing {
private List<String> strings;
public List<String> getStrings() {
return strings;
}
public void setStrings(List<String> strings) {
/*new*/ System.out.printf("Thing.setStrings called. Prev. this.strings: %s (size %s). Received strings param: %s", this.strings, (this.strings!=null ? this.strings.size():"null"),strings);
this.strings = strings;
/*new*/ System.out.printf(". New this.strings: %s (size %s).%n%n", this.strings, (this.strings!=null ? this.strings.size():"null"));
}
}
The extended class JaxbThing
import java.util.List;
import javax.xml.bind.annotation.*;
@XmlRootElement
public class JaxbThing extends Thing {
// @XmlElementWrapper(name = "list")
@XmlElementWrapper(name = "strings")
@XmlElement(name = "string")
public List<String> getStrings() {
return super.getStrings();
}
public void setStrings(List<String> string) {
/*new*/ System.out.printf("JaxbThing.setStrings called. Received strings param: %s (size %s)%n",string,(string !=null?string.size():"null"));
super.setStrings(string);
}
}
The test program:
import java.io.File;
import java.util.Arrays;
import javax.xml.bind.*;
public class Main {
public static void main(String[] args) {
System.out.println("---- Initiallizing");
JaxbThing t = new JaxbThing();
t.setStrings(Arrays.asList("a", "b", "c"));
try {
File f = new File("jaxb-file.xml");
JAXBContext context = JAXBContext.newInstance(JaxbThing.class);
System.out.println("---- Marshalling");
Marshaller m = context.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); /*new*/
m.marshal(t, f);
System.out.println("---- UnMarshalling");
Unmarshaller um = context.createUnmarshaller();
JaxbThing t2 = (JaxbThing) um.unmarshal(f);
System.out.println("---- Showing results");
System.out.println(t2.getStrings()); // I expect to see [a, b, c]
} catch (JAXBException e) {
e.printStackTrace();
}
}
}
The results are as follows (I have numbered the lines):
1 ---- Initiallizing
2 JaxbThing.setStrings called. Received strings param: [a, b, c] (size 3)
3 Thing.setStrings called. Prev. this.strings: null (size null). Received strings param: [a, b, c]. New this.strings: [a, b, c] (size 3).
4
5 ---- Marshalling
6 ---- UnMarshalling
7 JaxbThing.setStrings called. Received strings param: [] (size 0)
8 Thing.setStrings called. Prev. this.strings: null (size null). Received strings param: []. New this.strings: [] (size 0).
9
10 JaxbThing.setStrings called. Received strings param: [
] (size 1)
11 Thing.setStrings called. Prev. this.strings: [
] (size 1). Received strings param: [
]. New this.strings: [
] (size 1).
12
13 ---- Showing results
14 [
]
During unmarshalling, a new JaxbThing
is built, its JaxbThing.setStrings
is first called with a 0-length list of strings (line 7). Consequently, the Thing.setStrings
is called (line 8). The previous value of the object's strings field (this.strings
) was null. Then, the JaxbThing.setStrings
is called again (line 10), but now with a list of strings that contains an string with a '\n' character plus 4 blank spaces (I guess this corresponds to the indenting of the XML file). Two remarks:
JaxbThing.setStrings
is never called with the correct values (unless I haven’t seen it).JaxbThing.setStrings
(line 10) causes the second call to Thing.setStrings
(line 11). But now the previous value of this.strings
is not the 0-length list of strings set in the previous call to Thing.setStrings
in line 8. It is already the '\n' character plus 4 blank spaces that is passed as parameter.Any clue explaining this behavior?
Upvotes: 0
Reputation: 149017
The problems is that you are overriding a property from the parent class. JAXB thinks JAXBThing
has"
strings
(inherited from String
) mapped to the element strings
.strings
(defined on JAXBThing
mapped to the element string
under the element strings
.If you put a break point in setStrings
method on JAXBThing
you will see that it is first called with the correct data, and then it is called a second time with incorrect data that overrides the initial set (because it thinks they are different properties that share a setter).
You can always remove the Thing
class by annotating it with @XmlTransient
.
import java.util.List;
import javax.xml.bind.annotation.XmlTransient;
@XmlTransient
public class Thing {
private List<String> strings;
public List<String> getStrings() {
return strings;
}
public void setStrings(List<String> strings) {
System.out.println("Thing" + strings);
this.strings = strings;
}
}
For More Information
Upvotes: 2