Constructing related .NET objects (an "object graph") from XML files is a familiar challenge to most developers out there. Although it is not hard to write something like that: just read the tokens and fill the appropriate objects.
In most cases, the code produced is not very readable and hard to maintain, because the problem is usually implemented in a linear fashion with lots of loops and repeated (or similar) statements.
When faced with this problem, I usually use the "Factory Method" pattern to implement this. The idea is that every object class has its own FromXml() method which creates its own object according to the XML node passed in as a parameter.
For example, let's suppose we want to create a customer object from an XML segment where the customer name is a child element and the customer ID is an attirbute like:
<Customer id="CUST1"><Name>My Customer Name</Name></Customer>
The factory method would look like this (I'm using C# syntax for object construction):
public partial class Customer
{
public static Customer FromXml(XmlNode xmlNode)
{
Customer customer = new Customer {
Id = xmlNode.Attributes["id"].Value,
Name = xmlNode["Name"].InnerText,
Orders = new List<Order>()
};
return customer;
}
}
Now suppose each customer has a number of orders, which are located under the <Orders/> child element. So we use the Order object's factory method to create the Order object from the XML child nodes:
public partial class Customer
{
public static Customer FromXml(XmlNode xmlNode)
{
Customer customer = new Customer {
Id = xmlNode.Attributes["id"].Value,
Name = xmlNode["Name"].InnerText,
Orders = new List<Order>()
};
foreach (XmlNode xmlOrder in xmlNode.SelectNodes("Orders/Order"))
customer.Orders.Add(Order.FromXml(xmlOrder, customer));
return customer;
}
}
Got the picture? Now let's pull it all together, and parse the following XML file to our object graph containing "Customers", "Orders" and "OrderItems":
<?xml version="1.0"?>
<CustomerList>
<Customer id="CUST1">
<Name>Mr X</Name>
<Orders>
<Order number="5001">
<OrderItems>
<OrderItem>
<Description>Item 1</Description>
<Price>45.00</Price>
</OrderItem>
<OrderItem>
<Description>Item 2</Description>
<Price>99.00</Price>
</OrderItem>
</OrderItems>
</Order>
<Order number="5002">
<OrderItems>
<OrderItem>
<Description>Item 3</Description>
<Price>120.00</Price>
</OrderItem>
</OrderItems>
</Order>
</Orders>
</Customer>
<Customer id="CUST2">
<Name>John Doe</Name>
<Orders>
<Order number="5003">
<OrderItems>
<OrderItem>
<Description>Item 4</Description>
<Price>550.00</Price>
</OrderItem>
<OrderItem>
<Description>Item 5</Description>
<Price>41.50</Price>
</OrderItem>
</OrderItems>
</Order>
</Orders>
</Customer>
</CustomerList>
And the code:
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load("test.xml");
CustomerList customerList = CustomerList.FromXml(xmlDoc.DocumentElement);
public partial class CustomerList : List<Customer>
{
}
public partial class Customer
{
public string Id { get; private set; }
public string Name { get; set; }
public List<Order> Orders { get; private set; }
}
public partial class Order
{
public int OrderNumber { get; private set; }
public List<OrderItem> OrderItems { get; private set; }
public Customer Customer { get; set; }
}
public partial class OrderItem
{
public string Description { get; set; }
public decimal Price { get; set; }
public Order Order { get; set; }
}
public partial class CustomerList
{
public static CustomerList FromXml(XmlNode xmlNode)
{
CustomerList list = new CustomerList();
foreach (XmlNode xmlCustomer in xmlNode.SelectNodes("Customer"))
list.Add(Customer.FromXml(xmlCustomer));
return list;
}
}
public partial class Customer
{
public static Customer FromXml(XmlNode xmlNode)
{
Customer customer = new Customer {
Id = xmlNode.Attributes["id"].Value,
Name = xmlNode["Name"].InnerText,
Orders = new List<Order>()
};
foreach (XmlNode xmlOrder in xmlNode.SelectNodes("Orders/Order"))
customer.Orders.Add(Order.FromXml(xmlOrder, customer));
return customer;
}
}
public partial class Order
{
public static Order FromXml(XmlNode xmlNode, Customer customer)
{
Order order = new Order {
OrderNumber = Int32.Parse(xmlNode.Attributes["number"].Value),
Customer = customer,
OrderItems = new List<OrderItem>()
};
foreach (XmlNode xmlItem in xmlNode.SelectNodes("OrderItems/OrderItem"))
order.OrderItems.Add(OrderItem.FromXml(xmlItem, order));
return order;
}
}
public partial class OrderItem
{
public static OrderItem FromXml(XmlNode xmlNode, Order order)
{
return new OrderItem {
Description = xmlNode["Description"].InnerText,
Price = Decimal.Parse(xmlNode["Price"].InnerText),
Order = order
};
}
}
The advantages of using this pattern are obvious:
- Very readable (and maintainable) code
- The implementation details on how to read a particular type of object are located in the class itself, making it very easy to make a small change (for example adding a new property to an object)
- Easy to read recursive object graphs
- Properties that should be read-only can be made read-only (no messing with "internal" properties)
Note that a similar pattern can be used for writing objects to an XML file. Just create a member method "ToXml()".

•