Suppose that our client has defined a base XML file that we should make XStream read/write:
<blog author="Guilherme Silveira"> <entry> <title>first</title> <description>My first blog entry.</description> </entry> <entry> <title>tutorial</title> <description> Today we have developed a nice alias tutorial. Tell your friends! NOW! </description> </entry> </blog>
Based on the XML file above we shall create some model classes and configure XStream to write/read from this format.
First things first, the classes which shall represent our xml files are shown next, beginning with a simple Blog:
package com.thoughtworks.xstream; public class Blog { private Author author; private List entries = new ArrayList(); public Blog(Author author) { this.author = author; } public void add(Entry entry) { entries.add(entry); } public List getContent() { return entries; } }
A basic author with name:
package com.thoughtworks.xstream; public class Author { private String name; public Author(String name) { this.name = name; } public String getName() { return name; } }
A blog entry contains a title and description:
package com.thoughtworks.xstream; public class Entry { private String title, description; public Entry(String title, String description) { this.title = title; this.description = description; } }
Although we did not create many getters/setters its up to you to create those you wish or those which make sense.
We can easily instantiate a new blog and use it with xstream:
public static void main(String[] args) { Blog teamBlog = new Blog(new Author("Guilherme Silveira")); teamBlog.add(new Entry("first","My first blog entry.")); teamBlog.add(new Entry("tutorial", "Today we have developed a nice alias tutorial. Tell your friends! NOW!")); XStream xstream = new XStream(); System.out.println(xstream.toXML(teamBlog)); }
And the resulting XML is not so nice as we would want it to be:
<com.thoughtworks.xstream.Blog> <author> <name>Guilherme Silveira</name> </author> <entries> <com.thoughtworks.xstream.Entry> <title>first</title> <description>My first blog entry.</description> </com.thoughtworks.xstream.Entry> <com.thoughtworks.xstream.Entry> <title>tutorial</title> <description> Today we have developed a nice alias tutorial. Tell your friends! NOW! </description> </com.thoughtworks.xstream.Entry> </entries> </com.thoughtworks.xstream.Blog>
The first thing we shall change is how XStream refers to the com.thoughtworks.xstream.Blog class. We shall name it simply blog: let's create an alias called blog to the desired class:
xstream.alias("blog", Blog.class);
Using the same idea, we can alias the 'Entry' class to 'entry':
xstream.alias("entry", Entry.class);
The result now becomes:
<blog> <author> <name>Guilherme Silveira</name> </author> <entries> <entry> <title>first</title> <description>My first blog entry.</description> </entry> <entry> <title>tutorial</title> <description> Today we have developed a nice alias tutorial. Tell your friends! NOW! </description> </entry> </entries> </blog>
Now let's implement what was called an implicit collection: whenever you have a collection which doesn't need to display it's root tag, you can map it as an implicit collection.
In our example, we do not want to display the entries tag, but simply show the entry tags one after another.
A simple call to the addImplicitCollection method shall configure XStream and let it know that we do not want to write the entries tag as described above:
package com.thoughtworks.xstream; import java.util.ArrayList; import java.util.List; public class Test { public static void main(String[] args) { Blog teamBlog = new Blog(new Author("Guilherme Silveira")); teamBlog.add(new Entry("first","My first blog entry.")); teamBlog.add(new Entry("tutorial", "Today we have developed a nice alias tutorial. Tell your friends! NOW!")); XStream xstream = new XStream(); xstream.alias("blog", Blog.class); xstream.alias("entry", Entry.class); xstream.addImplicitCollection(Blog.class, "entries"); System.out.println(xstream.toXML(teamBlog)); } }
Pay attention to the addImplicitCollection method call: it describes which class and which member variable shall assume the behaviour we described.
The result is almost what we wanted:
<blog> <author> <name>Guilherme Silveira</name> </author> <entry> <title>first</title> <description>My first blog entry.</description> </entry> <entry> <title>tutorial</title> <description> Today we have developed a nice alias tutorial. Tell your friends! NOW! </description> </entry> </blog>
The next step is to set the author member variable as an XML attribute. In order to do this, we shall tell XStream to alias the author field of the Blog class as an "author" attribute.
xstream.useAttributeFor(Blog.class, "author");
And now it leaves us with one problem: how does XStream converts an Author in a String so it can be written as a xml tag attribute?
Let's use the SimpleValueConverter and implement our own Author converter:
class AuthorConverter implements SingleValueConverter { }
The first method to implement tells XStream which types it can deal with:
public boolean canConvert(Class type) { return type.equals(Author.class); }
The second one is used to extract a String from an Author:
public String toString(Object obj) { return ((Author) obj).getName(); }
And the third one does the opposite job: takes a String and returns an Author:
public Object fromString(String name) { return new Author(name); }
Finally, the entire single value converter, responsible for converting Strings to Objects (Authors in this case) is:
class AuthorConverter implements SingleValueConverter { public String toString(Object obj) { return ((Author) obj).getName(); } public Object fromString(String name) { return new Author(name); } public boolean canConvert(Class type) { return type.equals(Author.class); } }
And let's register this converter:
public class Test { public static void main(String[] args) { Blog teamBlog = new Blog(new Author("Guilherme Silveira")); teamBlog.add(new Entry("first","My first blog entry.")); teamBlog.add(new Entry("tutorial", "Today we have developed a nice alias tutorial. Tell your friends! NOW!")); XStream xstream = new XStream(); xstream.alias("blog", Blog.class); xstream.alias("entry", Entry.class); xstream.addImplicitCollection(Blog.class, "entries"); xstream.useAttributeFor(Blog.class, "author"); xstream.registerConverter(new AuthorConverter()); System.out.println(xstream.toXML(teamBlog)); } }
The result?
<blog author="Guilherme Silveira"> <entry> <title>first</title> <description>My first blog entry.</description> </entry> <entry> <title>tutorial</title> <description> Today we have developed a nice alias tutorial. Tell your friends! NOW! </description> </entry> </blog>
There useAttributeFor method is overloaded with other similar functionalities, including a version which receives an extra String at the end (Class, String, String) telling XStream to alias that field to another name. For example: useAttributeFor(Blog.class, "author", "auth") would map the "author" field as an attribute called "auth".
Using aliases and converters gives you enough power to configure your XML in almost any way you desire.
Don't forget to read the converter tutorial to see other type of converters that you can create using XStream.