Mapping Inheritance Cleanly with XStream

A fast-running woodland stream

XStream is a wonderfully simple library for mapping Java classes to XML and back. However, it has a quirk in the way it handles inheritance: if a field refers to an object that is derived from the field's static type, XStream adds a class attribute to the element that corresponds to the field. Often the XML contains enough information to decide which type of object should be unmarshalled, and so the class element is unnecessary. However, it's not obvious how one can stop XStream writing the class element. There is no information about how to do it in the documentation and although the question comes up regularly on the XStream mailing lists, I found no good answers there either. I've finally worked out how to do it, so here's a short tutorial. Hopefully this will stop other XStream users tearing their hair out like I did!

Let's look at some Java classes that will make XStream add a class attribute when it marshals them to XML.

Suppose we have a calculation grid that accepts calculation requests sent as XML messages. A calculation request contains some information about the request (who sent it, what is the request's priority) and some calculation parameters (market data, for example). The message can either contain the parameters inline or a link to parameters that are stored remotely.

We're using XStream to generate those request messages from our Java classes. We have two classes that represent calculation parameters, one that represents the actual parameters and one that represents a link to remote parameters. They both implement the CalculationParameters interface.

public class LocalCalculationParameters implements CalculationParameters {
    ... lots of fields here ...
}
public class RemoteCalculationParameters implements CalculationParameters {
    private final URL url;

    public RemoteCalculationParameters(URL url) {
        this.url = url;
    }

    ...
}

We have a calculation request class that carries the parameters along with other information and doesn't care if the parameters are local (carried within the message) or remote (linked from the message).

public class CalculationRequest {
    private final String sender;
    private final Priority priority;
    private final CalculationParameters parameters;

    public Message(String sender, Priority priority, CalculationParameters parameters) {
        this.sender = sender;
        this.priority = priority;
        this.parameters = parameters;
    }

    ...
}

When we marshall a CalculationRequest to XML, a request containing LocalCalculationParameters should be marshalled like:

<request>
  <sender>Bob</sender>
  <priority>NORMAL</priority>
  <parameters>
    ... lots of content here ...
  </parameters>
</plate>

And one containing RemoteCalculationParameters should be marshalled like:

<request>
  <sender>Bob</sender>
  <priority>NORMAL</priority>
  <parameters href="http://jobs.example.com/bobs-job.xml"/>
</plate>

It is easy to configure XStream to use simple element names for classes and convert the url field of the RemoteCalculationParameters into an attribute named href. However, because the static type of the CalculationRequest's parameters field is an interface, CalculationParameters, XStream adds a class attribute to the parameters element to identify the actual implementation that has been marshalled. For example, RemoteCalculationParameters get marshalled like:

<request>
  <sender>Bob</sender>
  <priority>NORMAL</priority>
  <parameters class="com.example.calcgrid.api.RemoteCalculationParameters"
              href="http://jobs.example.com/bobs-job.xml"/>
</request>

And the LocalCalculationParameters get marshalled like:

<request>
  <sender>Bob</sender>
  <priority>NORMAL</priority>
  <parameters class="com.example.calcgrid.api.LocalCalculationParameters">
    ... lots of content here ...
  </parameters>
</request>

This looks ugly and leaks implementation details into the XML format where they don't belong. We don't want to tie consumers of the XML to our implementation because we will break their implementation when we refactor our code.

The class attribute is unnecessary for our CalculationParameters types because we can determine which concrete type to unmarshall from the contents of XML itself: if the parameters element has an href attribute, it represents RemoteCalculationParameters, otherwise it represents LocalCalculationParameters. We can implement this rule as a Converter class:

public class CalculationParametersConverter implements Converter {
    private final Converter defaultConverter;
    private final ReflectionProvider reflectionProvider;

    public CalculationParametersConverter(Converter defaultConverter, ReflectionProvider reflectionProvider) {
        this.defaultConverter = defaultConverter;
        this.reflectionProvider = reflectionProvider;
    }

    public boolean canConvert(Class type) {
        return CalculationParameters.class.isAssignableFrom(type);
    }

    public void marshal(Object o, HierarchicalStreamWriter out, MarshallingContext context) {
        defaultConverter.marshal(o, out, context);
    }

    public Object unmarshal(HierarchicalStreamReader in, UnmarshallingContext context) {
        String href = in.getAttribute("href");
        Class<?> resultType = 
            (href == null) ? LocalCalculationParameters.class 
                           : RemoteCalculationParameters.class;
        Object result = reflectionProvider.newInstance(resultType);
        return context.convertAnother(result, resultType, defaultConverter);
    }
}

And register the Converter with XStream like this:

xstream.registerConverter(new CalculationParametersConverter(
        xstream.getConverterLookup().lookupConverterForType(CalculationParameters.class),
        xstream.getReflectionProvider()));

Now we must configure XStream to not write the class attribute. This is extremely non-obvious: to do so we must add all the concrete classes as the default implementation of their supertype.

xstream.addDefaultImplementation(LocalCalculationParameters.class, CalculationParameters.class);
xstream.addDefaultImplementation(RemoteCalculationParameters.class, CalculationParameters.class);

Now the generated XML looks just how we want it.

This works with multiple subclasses and with SingleValueConverters. As long as you can determine the concrete type to be unmarshalled from the contents of the marshalled element, you can use this technique to elide the class attribute and get cleaner XML.

Update: I've submitted a patch to XStream that adds a convenient API call for creating these kinds of mappings. You can apply the patch yourself or vote for the JIRA issue if you'd like to see it in a future version of XStream.

Image by audreyjm529, distributed under the Creative Commons Attribution license.

Copyright © 2009 Nat Pryce. Posted 2009-03-26. Share it.

Comments powered by Disqus