Search code examples
javaxstream

Deserialize a final list attribute


Suppose we have a class Test like:

public class Test {
    private final String name;
    private final List<String> list = new ArrayList<>();

    public Test(String name) {
        this.name = name;
    }

    void add(String s) {
        list.add(s);
    }

    void print() {
        System.out.println("name: " + name);
        for (String s : list) {
            System.out.println(" - " + s);
        }
    }
}

Without XSteam the invariant

this.list != null

holds every time.

But if we look in the 4th test in

public static void main(String[] args) {
    final XStream xstream = new XStream();
    xstream.alias("test", Test.class);

    // Serialize
    final Test test1 = new Test("XYZ");
    test1.add("One");
    test1.add("Two");

    //@formatter:off
    /* name: XYZ
     *  - One
     *  - Two
     */
    //@formatter:on
    test1.print();

    //@formatter:off
    /* <test>
     *   <name>XYZ</name>
     *   <list>
     *     <string>One</string>
     *     <string>Two</string>
     *   </list>
     * </test>
     */
    //@formatter:on
    System.out.println(xstream.toXML(test1));

    // Deserialize with one list entry
    final String xmlTest2 = "<test><name>XYZ</name><list><string>One</string></list></test>";
    final Test test2 = (Test) xstream.fromXML(xmlTest2);
    //@formatter:off
    /* <test>
     *   <name>XYZ</name>
     *   <list>
     *     <string>One</string>
     *   </list>
     * </test>
     */
    //@formatter:on
    test2.print();

    // Deserialize with empty list
    final String xmlTest3 = "<test><name>XYZ</name><list /></test>";
    final Test test3 = (Test) xstream.fromXML(xmlTest3);
    //@formatter:off
    /* name: XYZ
     */
    //@formatter:on
    test3.print();

    // Deserialize without list-tag
    final String xmlTest4 = "<test><name>XYZ</name></test>";
    final Test test4 = (Test) xstream.fromXML(xmlTest4);
    //@formatter:off
    /* name: XYZ
     * Exception in thead ... NullPointerException
     */
    //@formatter:on
    test4.print();
}

we see a NullPointerException, because list was not initialized.

I'd like to have the list-element in the XML optional similiar to test4. What can I do? Because there are many classes in my datamodel similiar to Test, I don't want to write a Converter for every class. But suppose I would write a Converter, how can I set the final attribute name?


Solution

  • XStream uses munged constructors (http://stackoverflow.com/questions/1426106/why-are-constructors-returned-by-reflectionfactor-newconstructorforserialization) in enhanced mode (default). You can change this behavior by initializing XStream in pure mode:

    XStream xstream = new XStream(new PureJavaReflectionProvider());
    

    Another option is to access variables using getters and implement lazy initialization.

    public class Test {
    private final String name;
    private List<String> list;
    
    public Test(String name) {
        this.name = name;
    }
    
    void add(String s) {
        list.add(s);
    }
    
    List<String> getList() {
      if (list == null) {
        list = new ArrayList<>();
      }
      return list;
    }
    
    void print() {
        System.out.println("name: " + name);
        for (String s : getList()) {
            System.out.println(" - " + s);
        }
    }
    }