Convert XML into POJO using HierarchicalStreamReader

In this example I would like to show how unmarshall XML into a POJO using HierarchicalStreamReader. I will introduce how to use  XStream and a custom XML-to-POJO converter, describe a case study and develop an application to show the solution.

Purchase History Example
Consider the following XML. It represents represents a purchase history of a customer, that has purchased different items during a week:

<PURCHASE>
	<ITEM>
		<SUBITEM>
			<CODE>PDCT-001</CODE>
			<NAME>Product-1</NAME>
			<DAY5>1.75</DAY5>
			<TOTAL>1.75</TOTAL>
		</SUBITEM>
		<SUBITEM>
			<CODE>PDCT-002</CODE>
			<NAME>Product-2</NAME>
			<DAY7>1.00</DAY7>
			<DAY6>1.75</DAY6>
			<TOTAL>2.75</TOTAL>
		</SUBITEM>
		<SUBITEM>
			<CODE>PDCT-003</CODE>
			<NAME>Product-3</NAME>
			<DAY7>4.50</DAY7>
			<DAY6>2.00</DAY6>
			<DAY5>3.00</DAY5>
			<DAY4>2.25</DAY4>
			<TOTAL>11.75</TOTAL>
		</SUBITEM>
		<SUBITEM>
			<CODE>PDCT-004</CODE>
			<NAME>Product-4</NAME>
			<DAY7>3.50</DAY7>
			<DAY6>3.75</DAY6>
			<DAY5>2.75</DAY5>
			<DAY1>3.75</DAY1>
			<TOTAL>13.75</TOTAL>
		</SUBITEM>
		<REFERENCE>9571-EDGDFG-DGSNJE-1837</REFERENCE>
		<GRANDTOTAL>30</GRANDTOTAL>
	</ITEM>
</PURCHASE>

To unmarshall the XML, I created three POJOs to represent the hierarchy of XML elements. The domain objects Purchase, Item and SubItem are involved.  The Purchase  has an Item, that has a collection of SubItem objects. My customer XML converter will transform customer’s purchase history from XML into a domain model using the POJOs.

I am not going to describe the full structure of the POJOs here, as I want to concentrate on talking about the XML converter . The source code as Eclipse project is attached to the current article, so you can download it.

I use XStream, which is a simple open-source Java library for serialize objects to and from XML. The following is the unit test case code snippet that show how I register my custom converter that is used for XML unmarshalling:

XStream xstream = new XStream(new DomDriver());
xstream.alias("PURCHASE", Purchase.class);
xstream.registerConverter(new PurchaseConverter());
Purchase purchase = (Purchase) xstream.fromXML(xmlContent);

I created an alias for Purchase that matches XML root element and registered my custom converter. The converter is a specific converter that knows how to handle this particular XML example, so it is not a generic solution. The converter code as follows:

package asia.javabeans.streamreader.util;

import asia.javabeans.streamreader.domain.Purchase;
import asia.javabeans.streamreader.domain.SubItem;

import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;

/**
 * Current class represents converter for a Purchase XML.
 *
 * @author alexander.zagniotov
 *
 */
final public class PurchaseConverter implements Converter {

	private static final int NUM_OF_DAYS = 7;

	public void marshal(Object value, HierarchicalStreamWriter writer,
			MarshallingContext context) {
	}

	public Object unmarshal(HierarchicalStreamReader reader,
			UnmarshallingContext context) {

		Purchase purchase = new Purchase();

                //while there are unread XML element children and the reader has not reached the closing PURCHASE element
		while (!(reader.getNodeName().equals(XmlElements.PURCHASE) && !reader
				.hasMoreChildren())) {

			if (reader.hasMoreChildren()) {
				reader.moveDown(); // Move down to ITEM element

				// Process child SUBITEM elements of the parent ITEM
				if (reader.getNodeName().equals(XmlElements.ITEM)) {
					iterateThroughItemSubitems(reader, XmlElements.ITEM,
							XmlElements.SUBITEM, purchase);
				}
			}
                        //store value of REFERENCE element in a POJO
			if (reader.getNodeName().equals(XmlElements.REFERENCE)) {
				purchase.setReference(reader.getValue());
			}

			reader.moveUp(); // Move back up to PURCHASE element
		}

		return purchase;
	}

	private Purchase iterateThroughItemSubitems(
			HierarchicalStreamReader reader, String parentNameName,
			String childNodeName, Purchase purchase) {
		boolean hasSubitemNodes = true;

		while (reader.getNodeName().equals(parentNameName) && hasSubitemNodes) {
			reader.moveDown(); // Move down to SUBITEM element

			// We have SUBITEM element that we can read
			if (reader.getNodeName().equals(childNodeName)) {
				purchase = iterateThroughSubitemChildren(reader, childNodeName,
						purchase);
			} else { // No more children SUBITEM elements to read
				break;
			}

			reader.moveUp(); // Move back up to parent ITEM element
			continue;
		}

		return purchase;
	}

	private Purchase iterateThroughSubitemChildren(
			HierarchicalStreamReader reader, String nodeName, Purchase purchase) {
		boolean hasChildrenNodes = true;

		SubItem subItem = new SubItem();

		while (reader.getNodeName().equals(nodeName) && hasChildrenNodes) {
			// Move down to the children of current SUBITEM element
			reader.moveDown();

			if (reader.getNodeName().equals(XmlElements.CODE)) {
				String subItemCode = reader.getValue();
				subItem.setCode(subItemCode);
				reader.moveUp(); // Move back up to parent SUBITEM
				continue;
			}

			if (reader.getNodeName().equals(XmlElements.NAME)) {
				String subItemName = reader.getValue();
				subItem.setName(subItemName);
				reader.moveUp(); // Move back up to parent SUBITEM
				continue;
			}

			if (reader.getNodeName().startsWith(XmlElements.DAY)) {

				for (int i = 1; i <= NUM_OF_DAYS; i++) {
					String xmlDay = XmlElements.DAY + i;

					if (reader.getNodeName().equals(xmlDay)) {

						double hours = Double.parseDouble(reader.getValue());
						subItem.addToDailyHours(xmlDay, hours);
						purchase.addToGrandTotal(hours);
						break;
					}
				}

				reader.moveUp(); // Move back up to parent SUBITEM
				continue;
			}

			if (reader.getNodeName().equals(XmlElements.TOTAL)) {
				hasChildrenNodes = false; // This is the last child of parent SUBITEM

				String itemTotalStr = reader.getValue();
				subItem.setItemTotal(Double.parseDouble(itemTotalStr));
				purchase.getItem().addSubItem(subItem);
				reader.moveUp(); // Move back up to parent SUBITEM
				continue;
			}
		}
		return purchase;
	}

	@SuppressWarnings("unchecked")
	public boolean canConvert(Class clazz) {
		return clazz.equals(Purchase.class);
	}

}

As you can see, the key to operate a stream reader is to move it down to a child element for reading and back up to a parent element. Each time I move the reader down, it moves to an element that has not being read yet.

Conclusion

I presented a simple example where I show how to unmarshall XML into Java POJOs using HierarchicalStreamReader and XStream. The source code is attached.

I hope this tutorial was helpful to some of you,
Regards