Tuesday, 2 November 2010

Taming XStream part 1

I have used XStream for a while for the general purpose duty of Java object serialisation to XML. However I have not attempted to deserialise a non XStream generated piece of XML into Java objects before.


Whilst attempting to do this I realised that it is not as simple as it first seems. After searching the few tutorials there are on the website it became clear there is little documentation to cover this process.


For this guide, I assume you have read Two Minute Tutorial and Alias Tutorial.


We will start with the following XML we need to deserialise and work with:


<?xml version="1.0" encoding="UTF-8"?>
<xml>
    <ips>
        <ip>192.168.*.1</ip>
        <ip>127.0.0.1</ip>
        <ip>10.0.*</ip>
    </ips>
</xml>

They key concept with XStream is that each layer of tags is an encapsulated level in the object hierarchy. To use the above example, there must be an object which maps to xml, and an encapsulated object within xml which maps to ips.


So, we will create a suitable data object. I have also included the xstream statements that map to this data object:


private class XML {
private IPData ips;
public XML(IPData ips) {
this.ips = ips;
}
}

// This maps as follows
xstream.alias("xml", XML.class);

With XStream, when we wish to define sub tag we have two choices. We can either say that the class needs the sub tag as a constructor argument. Or we can use the aliasField function to map the sub tag to a named field within the class as below:


private class XML {
private IPData ips;
}

// This maps as follows
xstream.alias("xml", XML.class);
xstream.aliasField("ips", XML.class, "ips");

Regardless of the approach we use. The next problem to tackle is the next level down. The ips tag contains three ip tags which contain string data. The important thing here is that the mapping we create describes a collection. We can do this with the addImplicitCollection function:


private class XML {
private IPData ips;
public XML(IPData ips) {
this.ips = ips;
}
}

private class IPData {
List<String> list = new ArrayList<String>();
}

// With the following mapping
xstream.alias("xml", XML.class);
xstream.alias("ips", IPData.class);
xstream.addImplicitCollection(IPData.class, "list", "ip", String.class);

In this case, we have defined that the IPData class maps to the ips tag. Then we have defined that there is an implicit collection which maps to the field list and contains Strings.


This works rather nicely as the following completed code segment demonstrates:


package xstream;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;
import java.util.ArrayList;
import java.util.List;

public class One {
public static void main(String[] args) {
XStream xstream = new XStream(new DomDriver());

xstream.alias("xml", XML.class);
xstream.alias("ips", IPData.class);
xstream.addImplicitCollection(IPData.class, "list", "ip", String.class);

XML data = (XML) xstream.fromXML(
One.class.getResourceAsStream("ip-ranges-one.xml"));

for (String s : data.ips.list) {
System.out.println(s);
}
}

private class XML {
private IPData ips;
public XML(IPData ips) {
this.ips = ips;
}
}

private class IPData {
List<String> list = new ArrayList<String>();
}
}

No comments:

Post a Comment