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't Born. It's Grown. Here'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 ' 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't Born. It's Grown. Here'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't Born. It's Grown. Here'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.
Thanks for this interesting and detailed post, I was myself stuck with the “java.util.Arrays$ArrayList” problem.
Great, you found the solution to our unit test problem. Thanks!