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:

[xml]
<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>
[/xml]

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:

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

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:

[java]
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);
}

}
[/java]

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

 

One thought on “Convert XML into POJO using HierarchicalStreamReader