Home > Java, open source, Uncategorized > XStream introduction and Java Collections serialization problem workaround

XStream introduction and Java Collections serialization problem workaround


XStream is an open-source Java library for converting objects to and from XML.  I’ll introduce how to use the library, show a problem I ran into that was a bit difficult to track down, as well as a workaround.

Library example

Imagine we are modeling a library in XML.  This will be much simplified for the purposes of illustration.  A sample XML library document might look like the following:

<library>
  <books>
    <book>
      <title>The Talent Code: Greatness Isn't Born. It's Grown. Here's How.</title>
      <author>Daniel Coyle</author>
    </book>
  </books>
</library>

Let’s write some Java classes to represent this hierarchy.

public class Library {
    public List<Book> books;

    public Library(List<Book> books) {
        this.books = books;
    }

    @Override
    public String toString() {
        StringBuilder b = new StringBuilder();
        b.append("Library with " + books.size() + " books:\n");
        for (Book book : books) {
            b.append(book.toString());
            // Skip a line
            b.append("\n");
        }
        return b.toString();
    }

    public static class Book {
        private String title;
        private String author;

        public Book(String title, String author) {
            this.title = title;
            this.author = author;
        }

        @Override
        public String toString() {
            return "\"" + title + "\" by " + author;
        }
    }

    public static void main(String[] args) {
        List<Book> books = new ArrayList<Book>();
        books.add(new Book("The Talent Code: Greatness Isn't Born. It's Grown. Here's How", "Daniel Coyle"));

        Library lib = new Library(books);
        // Handles conversion of our objects into XML
        XStream stream = new XStream(new DomDriver());

        String xml = stream.toXML(lib);
        // fromXML returns a general Object; need to cast it into a Library
        Library lib2 = (Library) stream.fromXML(xml);

        System.out.println(xml);
        System.out.println(lib);
        System.out.println(lib2);
        System.out.println("Libraries equal: " + lib.toString().equals(lib2.toString()));
    }
}

This is not production worthy by any means; in general comparing the string representations of objects to test for equality is a very bad idea.  But writing a foolproof equals method is very difficult and irrelevant to the point of this post.  Here is the output produced by running the program:

<Library>
  <books>
    <Library_-Book>
      <title>The Talent Code: Greatness Isn&apos;t Born. It&apos;s Grown. Here&apos;s How</title>
      <author>Daniel Coyle</author>
    </Library_-Book>
  </books>
</Library>
Library with 1 books:
"The Talent Code: Greatness Isn't Born. It's Grown. Here's How" by Daniel Coyle

Library with 1 books:
"The Talent Code: Greatness Isn't Born. It's Grown. Here's How" by Daniel Coyle
Libraries equal: true

This is somewhat close to what we want, but not exactly.  (Note that the &apos; stuff in the middle of the title is not a mistake; I was imprecise by including apostrophes in the original XML example.  See here for an explanation of why apostrophes are treated specially in XML). Let’s start by aliasing some of the tag names.  The relevant change is as follows:

XStream stream = new XStream(new  DomDriver());
stream.alias("library", Library.class);
stream.alias("book",  Book.class);

Output:

<library>
  <books>
    <book>
      <title>The Talent Code: Greatness Isn&apos;t Born. It&apos;s Grown. Here&apos;s How</title>
      <author>Daniel Coyle</author>
    </book>
  </books>
</library>

Initial success

OK, looks like things are working!  But the way of constructing the list is not how I normally create lists.  Let’s make sure the serialization works for alternative means of List construction.

Book talentCode =  new Book("The Talent Code: Greatness Isn't Born. It's Grown. Here's  How", "Daniel Coyle");
Library lib = new  Library(Arrays.asList(talentCode));

For you who haven’t seen the use of Arrays.asList, it’s a method to convert an array (or varargs) into a List.  It’s important for compatibility between array based and collection based frameworks.  The corresponding method in the opposite direction, from Collections to arrays is the Collections.toArray.  For the truly nerdy, here’s an interesting discussion of possible implications of calling toArray in a multithreaded environment.
Anyways, here’s the output.

<library>
  <books class="java.util.Arrays$ArrayList">
    <a class="book-array">
      <book>
        <title>The Talent Code: Greatness Isn&apos;t Born. It&apos;s Grown. Here&apos;s How</title>
        <author>Daniel Coyle</author>
      </book>
    </a>
  </books>
</library>
Library with 1 books:
"The Talent Code: Greatness Isn't Born. It's Grown. Here's How" by Daniel Coyle

Library with 1 books:
"The Talent Code: Greatness Isn't Born. It's Grown. Here's How" by Daniel Coyle

Libraries equal: true

Hmmm.  The XML serialization/deserialization still works but it’s sure not pretty.  What’s going on here?  Why did it print “books” earlier but now it throws in all that ugly stuff about “java.util.Arrays$ArrayList”?

The answer is that XStream has a powerful set of converters that handle mapping Java constructs to and from XML.  The relevant converter is the CollectionConverter.  Here is the JavaDoc description, and the solution to the mystery:

Converts most common Collections (Lists and Sets) to XML, specifying a nested element for each item.

Supports java.util.ArrayList, java.util.HashSet, java.util.LinkedList, java.util.Vector and java.util.LinkedHashSet.

The problem is that creating a List via the Arrays.toList method does not create an instance of any of these classes.  Instead, it returns an instance of an inner class named ArrayList (not to be confused with the java.util.ArrayList).  Read the source of the java.util.Arrays class if you are interested in the implementation details.

What’s the workaround?  Don’t blindly accept the list of books that is passed into the constructor.  Instead, make a defensive copy.

public List<Book> books = new  ArrayList<Book>();

public Library(List<Book>  books) {
      this.books.addAll(books);
 }

Sure enough, this fixes the problem, as the actual class of the list of books is now one that the converter supports.  Alternatively we could change the implementation of CollectionConverter to support the Arrays$ArrayList class, but it’s probably not worth it.

Conclusion

I introduced the XStream library for Java which handles the conversion of objects to and from XML.  I presented a very simple Java example of a Library and its collection of books, as well as a problem that arises from the way XStream converts collections into XML.

About these ads
  1. yohan
    June 15, 2012 at 4:44 am | #1

    Thanks for this interesting and detailed post, I was myself stuck with the “java.util.Arrays$ArrayList” problem.

  2. Jonas
    December 3, 2012 at 11:36 pm | #2

    Great, you found the solution to our unit test problem. Thanks!

  1. July 14, 2010 at 7:12 am | #1

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: