user1872904
user1872904

Reputation: 161

JAXB and inheritance works when marshaling; but not unmarshaling

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

Answers (2)

user1872904
user1872904

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:

  1. The JaxbThing.setStrings is never called with the correct values (unless I haven’t seen it).
  2. The second call to 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

bdoughan
bdoughan

Reputation: 149017

The problems is that you are overriding a property from the parent class. JAXB thinks JAXBThing has"

  1. A property called strings (inherited from String) mapped to the element strings.
  2. A property called 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

Related Questions