Quantcast
Channel: Baeldung
Viewing all articles
Browse latest Browse all 4536

XStream User Guide: Converting Objects to XML

$
0
0

1. Overview

In this tutorial, we’ll learn how to use the XStream library to serialize Java objects to XML.

2. Features

There are quite a few interesting benefits to using XStream to serialize and deserialize XML:

  • Configured properly, it produces very clean XML
  • Provides significant opportunities for customization of the XML output
  • Support for object graphs, including circular references
  • For most use cases, the XStream instance is thread-safe, once configured (there are caveats when using annotations)
  • Clear messages are provided during exception handling to help diagnose issues
  • Starting with version 1.4.7, we have security features available to disallow serialization of certain types

3. Project Setup

In order to use XStream in our project we will add the following Maven dependency:

<dependency>
    <groupId>com.thoughtworks.xstream</groupId>
    <artifactId>xstream</artifactId>
    <version>1.4.9</version>
</dependency>

 4. Basic Usage

The XStream class is a facade for the API. When creating an instance of XStream, we need to take care of thread safety issues as well:

XStream xstream = new XStream();

Once an instance is created and configured, it may be shared across multiple threads for marshalling/unmarshalling unless you enable annotation processing.

4.1. Drivers

Several drivers are supported, such as DomDriver, StaxDriver, XppDriver, and more. These drivers have different performance and resource usage characteristics.

The XPP3 driver is used by default, but of course we can easily change the driver:

XStream xstream = new XStream(new StaxDriver());

4.2. Generating XML

Let’s start by defining a simple POJO for – Customer:

public class Customer {

    private String firstName;
    private String lastName;
    private Date dob;

    // standard constructor, setters, and getters
}

Let’s now generate an XML representation of the object:

Customer customer = new Customer("John", "Doe", new Date());
String dataXml = xstream.toXML(customer);

Using the default settings, the following output is produced:

<com.baeldung.pojo.Customer>
    <firstName>John</firstName>
    <lastName>Doe</lastName>
    <dob>1986-02-14 03:46:16.381 UTC</dob>
</com.baeldung.pojo.Customer>

From this output, we can clearly see that the containing tag uses the fully-qualified class name of Customer by default.

There are many reasons we might decide that the default behavior doesn’t suit our needs. For example, we might not be comfortable exposing the package structure of our application. Also, the XML generated is significantly longer.

5. Aliases

An alias is a name we wish to use for elements rather than using default names.

For example, we can replace com.baeldung.pojo.Customer with customer by registering an alias for the Customer class. We can also add aliases for properties of a class. By using aliases, we can make our XML output much more readable and less Java-specific.

5.1. Class Aliases

Aliases can be registered either programmatically or using annotations.

Let’s now annotate our Customer class with @XStreamAlias:

@XStreamAlias("customer")

Now we need to configure our instance to use this annotation:

xstream.processAnnotations(Customer.class);

Alternatively, if we wish to configure an alias programmatically, we can use the code below:

xstream.alias("customer", Customer.class);

Whether using the alias or programmatic configuration, the output for a Customer object will be much cleaner:

<customer>
    <firstName>John</firstName>
    <lastName>Doe</lastName>
    <dob>1986-02-14 03:46:16.381 UTC</dob>
</customer>

5.2. Field Aliases

We can also add aliases for fields using the same annotation used for aliasing classes. For example, if we wanted the field firstName to be replaced with fn in the XML representation, we could use the following annotation:

@XStreamAlias("fn")
private String firstName;

Alternatively, we can accomplish the same goal programmatically:

xstream.aliasField("fn", Customer.class, "firstName");

The aliasField method accepts three arguments: the alias we wish to use, the class in which the property is defined, and the property name we wish to alias.

Whichever method is used the output is the same:

<customer>
    <fn>John</fn>
    <lastName>Doe</lastName>
    <dob>1986-02-14 03:46:16.381 UTC</dob>
</customer>

5.3. Default Aliases

There are several aliases pre-registered for classes – here’s a few of these:

alias("float", Float.class);
alias("date", Date.class);
alias("gregorian-calendar", Calendar.class);
alias("url", URL.class);
alias("list", List.class);
alias("locale", Locale.class);
alias("currency", Currency.class);

6. Collections

Now we will add a list of ContactDetails inside the Customer class.

private List<ContactDetails> contactDetailsList;

With default settings for collection handling, this is the output:

<customer>
    <firstName>John</firstName>
    <lastName>Doe</lastName>
    <dob>1986-02-14 04:14:05.874 UTC</dob>
    <contactDetailsList>
        <ContactDetails>
            <mobile>6673543265</mobile>
            <landline>0124-2460311</landline>
        </ContactDetails>
        <ContactDetails>
            <mobile>4676543565</mobile>
            <landline>0120-223312</landline>
        </ContactDetails>
    </contactDetailsList>
</customer>

Let’s suppose we need to omit the contactDetailsList parent tags, and we just want each ContactDetails element to be a child of the customer element. Let us modify our example again:

xstream.addImplicitCollection(Customer.class, "contactDetailsList");

Now, when the XML is generated, the root tags are omitted, resulting in the XML below:

<customer>
    <firstName>John</firstName>
    <lastName>Doe</lastName>
    <dob>1986-02-14 04:14:20.541 UTC</dob>
    <ContactDetails>
        <mobile>6673543265</mobile>
        <landline>0124-2460311</landline>
    </ContactDetails>
    <ContactDetails>
        <mobile>4676543565</mobile>
        <landline>0120-223312</landline>
    </ContactDetails>
</customer>

The same can also be achieved using annotations:

@XStreamImplicit
private List<ContactDetails> contactDetailsList;

7. Converters

XStream uses a map of Converter instances, each with its own conversion strategy. These convert supplied data to a particular format in XML and back again.

In addition to using the default converters, we can modify the defaults or register custom converters.

7.1. Modifying an Existing Converter

Suppose we weren’t happy with the way the dob tags were generated using the default settings. We can modify the custom converter for Date provided by XStream (DateConverter):

xstream.registerConverter(new DateConverter("dd-MM-yyyy", null));

The above will produce the output in “dd-MM-yyyy” format:

<customer>
    <firstName>John</firstName>
    <lastName>Doe</lastName>
    <dob>14-02-1986</dob>
</customer>

7.2. Custom Converters

We can also create a custom converter to accomplish the same output as in the previous section:

public class MyDateConverter implements Converter {

    private SimpleDateFormat formatter = new SimpleDateFormat("dd-MM-yyyy");

    @Override
    public boolean canConvert(Class clazz) {
        return Date.class.isAssignableFrom(clazz);
    }

    @Override
    public void marshal(
      Object value, HierarchicalStreamWriter writer, MarshallingContext arg2) {
        Date date = (Date)value;
        writer.setValue(formatter.format(date));
    }

    // other methods
}

Finally, we register our MyDateConverter class as below:

xstream.registerConverter(new MyDateConverter());

We can also create converters that implement the SingleValueConverter interface, which is designed to convert an object into a string.

public class MySingleValueConverter implements SingleValueConverter {

    @Override
    public boolean canConvert(Class clazz) {
        return Customer.class.isAssignableFrom(clazz);
    }

    @Override
    public String toString(Object obj) {
        SimpleDateFormat formatter = new SimpleDateFormat("dd-MM-yyyy");
        Date date = ((Customer) obj).getDob();
        return ((Customer) obj).getFirstName() + "," 
          + ((Customer) obj).getLastName() + ","
          + formatter.format(date);
    }

    // other methods
}

Finally, we register MySingleValueConverter:

xstream.registerConverter(new MySingleValueConverter());

Using MySingleValueConverter, the XML output for a Customer is as follows:

<customer>John,Doe,14-02-1986</customer>

7.3. Converter Priority

When registering Converter objects, is is possible to set their priority level, as well.

From the XStream javadocs:

The converters can be registered with an explicit priority. By default they are registered with XStream.PRIORITY_NORMAL. Converters of same priority will be used in the reverse sequence they have been registered. The default converter, i.e. the converter which will be used if no other registered converter is suitable, can be registered with priority XStream.PRIORITY_VERY_LOW. XStream uses by default the ReflectionConverter as the fallback converter.

The API provides several named priority values:

private static final int PRIORITY_NORMAL = 0;
private static final int PRIORITY_LOW = -10;
private static final int PRIORITY_VERY_LOW = -20;

8. Omitting Fields

We can omit fields from our generated XML using either annotations or programmatic configuration. In order to omit a field using an annotation, we simply apply the @XStreamOmitField annotation to the field in question:

@XStreamOmitField 
private String firstName;

In order to omit the field programmatically, we use the following method:

xstream.omitField(Customer.class, "firstName");

Whichever method we select, the output is the same:

<customer> 
    <lastName>Doe</lastName> 
    <dob>14-02-1986</dob> 
</customer>

9. Attribute Fields

Sometimes we may wish to serialize a field as an attribute of an element rather than as element itself. Suppose we add a contactType field:

private String contactType;

If we want to set contactType as an XML attribute, we can use the @XStreamAsAttribute annotation:

@XStreamAsAttribute
private String contactType;

Alternatively, we can accomplish the same goal programmatically:

xstream.useAttributeFor(ContactDetails.class, "contactType");

The output of either of the above methods is the same:

<ContactDetails contactType="Office">
    <mobile>6673543265</mobile>
    <landline>0124-2460311</landline>
</ContactDetails>

10. Concurrency

XStream’s processing model presents some challenges. Once the instance is configured, it is thread-safe.

It is important to note that processing of annotations modifies the configuration just before marshalling/unmarshalling. And so – if we require the instance to be configured on-the-fly using annotations, it is generally a good idea to use a separate XStream instance for each thread.

11. Conclusion

In this article, we covered the basics of using XStream to convert objects to XML. We also learned about customizations we can use to ensure the XML output meets our needs. Finally, we looked at thread-safety problems with annotations.

In the next article in this series, we will learn about converting XML back to Java objects.

The complete source code for this article can be downloaded from the linked GitHub repository.


Viewing all articles
Browse latest Browse all 4536

Trending Articles