Tweaking the Output
Out of the box, XStream is able to serialize most objects without the need for custom mappings to be setup. The XML produced is clean, however sometimes it's desirable to make tweaks to it. The most common use for this is when using XStream to read configuration files and some more human-friendly XML is needed.
Modification by configuration
A big part of the standard output of XStream can be configured. It is possible to set aliases for class types and field names that are mapped to XML tag or attribute names. Objects that can be represented as simple string value can be written as attributes. It is possible to omit fields or to flatten the structure for collections.
Aliases offer a simple way to use different tag or attribute names in the XML. The simplest and most commonly used tweak in XStream is to alias a fully qualified class to a shorter name. Another use case is a different field name for a class member. Aliases can be set for following elements:
- Class types mapped to XML tags
- Package names mapped to XML tags
- Field names mapped to XML tags
- Internal attributes names of XStream
The bold elements in the following example are affected:
<cat> <age>4</age> <name>Garfield</name> <owner type="StandardPerson"> <name>Jon Arbuckle</name> </owner> </cat>
Have a look at the Alias Tutorial for examples.
XML is quite clumsy to read for fields in separate tags that can represent their content in a short single string value. In such a case attributes can help to shorten the XML and increase readability:
<cat age="4" name="Garfield"> <owner class="StandardPerson" name="Jon Arbuckle"/> </cat>
Attributes are also presented in the Alias Tutorial.
For a proper deserialization XStream has to write the complete object graph into XML that is referenced by a single object. Therefore XStream has to find a representation that contains all aspects to recreate the objects.
However, some parts might be superfluous e.g. if a member field is lazy initialized and its content can be easily recreated. In such a case a field can be omitted using XStream.omitField(Class, String).
Implicit Collections, Arrays and Maps
Another use case are collections, arrays and maps. If a class has a field that is a one of those types, by default all of its elements are embedded in an element that represents the container object itself. By configuring the XStream with the XStream.addImplicitCollection(), XStream.addImplicitArray(), and XStream.addImplicitMap() methods it is possible to keep the elements directly as child of the class and the surrounding tag for the container object is omitted. It is even possible to declare more than one implicit collection, array or map for a class, but the elements must then be distinguishable to populate the different containers correctly at deserialization.
In the following example the Java type representing the farm may have two containers, one for cats and one for dogs:
<farm> <cat>Garfield</cat> <cat>Arlene</cat> <cat>Nermal</cat> <dog>Odie</dog> </farm>
The container might be a Collection, Array, or even a Map. In the latter case a member field of the value must have been defined, that is used as key in the deserialized map.
XStream is delivered with a lot of converters for standard types. Nevertheless most objects are processed by converters based on reflection. They will write the fields of a class in the sequence they are defined. It is possible to implement an algorithm for a different sequence or use an implementation that allows to define the sequence for each type separately using a FieldKeySorter. Similar functionality exists for Java Beans with the PropertySorter.
XStream writes and reads XML by default. However, the I/O layer is separated from the model and you can use implementations for other formats as well. A reader/writer pair is typically managed by a HierarchicalStreamDriver. XStream is delivering additional drivers for JSON and a compact binary format (see BinaryStreamDriver). There are also drivers to write an object graph into a DOM structure instead of a text based stream (available for W3C DOM, DOM4J, JDOM, XOM, and XPP DOM).
Sometimes customization is simply not enough to tweak the output. Depending on the use case it is fortunate to use specialized converters for own types, mapper implementations that control naming of things more globally or use specialized writers to influence the complete output.
Not all converters that are part of the XStream package are automatically registered. Some will only make sense for special types, others have parameters to tweak the behaviour. Most useful in the table of converters are the JavaBeanConverter, ToStringConverter, and ToAttributedValueConverter. Not to mention the separate Hibernate package of XStream.
As example can the ToAttributedValueConverter be used to define the owner's name directly as text value:
<cat age="4" name="Garfield"> <owner class="StandardPerson">Jon Arbuckle</> </cat>
Attributes are also presented in the Alias Tutorial.
Sometimes the object to serialize contains fields or elements, that have no friendly representation for human beings e.g. if a long value represents in reality a time stamp. In such cases XStream supports custom converters for arbitrary types. Have a look at the Converter Tutorial for advanced possibilities. Note, that a custom converter is not different to one that is delivered by XStream. It's simply your code.
In case of global adjustments it can be helpful to implement an own mapper. A mapper is used to name things and map between the name in the Java world to the name used in the XML representation. The alias mechanism described above is implemented as such a mapper that can be configured. A typical use case is dropping all prefixes for field names like underscores in the resulting XML or omitting the package part of class names.
However, keep in mind that the algorithm must work in both directions to support deserialization.
Custom Name Coders
Names used e.g. in XML for attributes and element tags might contain characters or character sequences that violate the syntax of the output format. Instead of using different aliases for such names that depend on the output format, you may use a NameCoder implementation that transforms an internally used name to an external form. For XML we use e.g. a NameCoder that encodes '$' characters, while the one used in JSON performs no operation at all.
Again, keep in mind that the algorithm of the name coder must support encoding and decoding of the names.
A custom writer can be used to affect the output completely. XStream itself delivers solutions for different use cases like the CompactWriter that does not insert any white spaces between the XML tags.
Another use case for such a writer is a wrapper to drop unwanted XML elements that XStream omits on its own. Especially if the written XML is not used for deserialization it can be useful to ignore internal attributes by a custom writer
Tweaking the own implementation
As shown, XStream can be configured and enhanced in multiple way, but sometimes it is easier to tweak the implementation of the serialized classes:
- Declaring a field transparent will omit it automatically from the processing
- Implementing a readResolve method that can be used to initialize additional fields
- Usage of annotations
Preprocessing or postprocessing
Never forget, you're dealing with XML! It is easy to transform XML with an XSLT. XStream is delivered with a SAXSource implementation, that allows an XStream instance to be the source of a XML transformer.
Look at the following stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="xml" omit-xml-declaration="yes" indent="no"/> <xsl:template match="/cat"> <xsl:copy> <xsl:apply-templates select="mName"/> </xsl:copy> </xsl:template> </xsl:stylesheet>
It is used here to remove the age of the cat on the fly (assuming XSLT is a string with the stylesheet above):
XStream xstream = new XStream(); xstream.alias("cat", Cat.class); TraxSource traxSource = new TraxSource(new Cat(4, "Garfield"), xstream); Writer buffer = new StringWriter(); Transformer transformer = TransformerFactory.newInstance().newTransformer( new StreamSource(new StringReader(XSLT))); transformer.transform(traxSource, new StreamResult(buffer));
The result in the buffer:
<cat> <mName>Garfield</mName> </cat>
XStream is designed to transform Java objects into a specific format and recreate these objects again. However, XStream's readers and writers are based on an event model that can be used easily to convert the pure data between formats. XStream contains the HierarchicalStreamCopier tool that utilizes a reader and a writer of XStream to perform the conversion:
HierarchicalStreamCopier copier = new HierarchicalStreramCopier(); HierarchicalStreamDriver binaryDriver = new BinaryDriver(); HierarchicalStreamDriver jsonDriver = new JettisonMappedXmlDriver(); // transform a org.dom4j.Document into a binary stream of XStream ByteArrayOutputStream baos = new ByteArrayOutputStream(); copier.copy(new Dom4JDriver.createReader(dom4JDocument), binaryDriver.createWriter(baos)); byte data = baos.getBytes(); // transform binary XStream data into JSON StringWriter strWriter = new StringWriter(); copier.copy(binaryDriver.createReader(data), jsonDriver.createWriter(strWriter)); String json = strWriter.toString(); // transform JSON into XML: strWriter = new StringWriter(); copier.copy(jsonDriver.createReader(new StringReader(json)), new PrettyPrintWriter(strWriter)); String xml = strWriter.toString();