Tuesday, March 17, 2009

Deserialization of XML using Groovy

Continuing on where I left off in my last post, I’d like to briefly explore going from xml to domain objects using Groovy and more specifically XmlSlurper.  In order for this to be a reasonable example, we’ll use the same xml format and domain objects that I’ve used in the last couple of posts.  I’ve included the code again here in case you haven’t been following this chain of posts on XmlSlurper (if you’re new to XmlSlurper you might want to check out my other posts on the topic for a quick tutorial).

Our business domain for this exercise consists of a Customer class and an Address class.  In our domain, a customer has an id number, a first name, a last name, and zero or more addresses.  An address consists of an id number, line1, line2, city, state, postal code. 

Customer class (Java):

   1: public class Customer {
   2:  
   3:     private int id;
   4:     private String firstName;
   5:     private String lastName;
   6:     private List addresses;
   7:  
   8:     public Customer() {
   9:         addresses = new ArrayList();
  10:     }
  11:  
  12:     public List getAddresses() {
  13:         return addresses;
  14:     }
  15:  
  16:     public void setAddresses(List address) {
  17:         this.addresses = address;
  18:     }
  19:  
  20:     public void addAddress(Address anAddress){
  21:         if(this.addresses == null)
  22:             this.addresses = new ArrayList();
  23:         
  24:         this.addresses.add(anAddress);
  25:     }
  26:  
  27:     public void removeAddress(Address anAddress){
  28:         this.addresses.remove(anAddress);
  29:     }
  30:  
  31:     public int getId() {
  32:         return id;
  33:     }
  34:  
  35:     public void setId(int id) {
  36:         this.id = id;
  37:     }
  38:  
  39:     public String getFirstName() {
  40:         return firstName;
  41:     }
  42:  
  43:     public void setFirstName(String firstName) {
  44:         this.firstName = firstName;
  45:     }
  46:  
  47:     public String getLastName() {
  48:         return lastName;
  49:     }
  50:  
  51:     public void setLastName(String lastName) {
  52:         this.lastName = lastName;
  53:     }
  54:  
  55:     /**
  56:      * This method returns a fully formatted name for this customer.
  57:      * @return - a string of the format 'firstName lastName'
  58:      */
  59:     public String returnFullName(){
  60:         if ("".equals(this.getFirstName()) || this.getFirstName() == null)
  61:             return this.getLastName();
  62:  
  63:         if ("".equals(this.getLastName()) || this.getLastName() == null)
  64:             return this.getFirstName();
  65:         
  66:         return this.getFirstName() + " " + this.getLastName();
  67:     }
  68:  
  69: }

Address class (Groovy):

   1: class Address {
   2:     int id
   3:     String addressLine1
   4:     String addressLine2
   5:     String city
   6:     String State
   7:     String postalCode
   8:  
   9:     /**
  10:     * Returns the address formated as a single line string.  If line1, city, state, or postal code are null,
  11:     * the entire string will return null
  12:     **/
  13:     def String formattedAddress() {
  14:         if (addressLine1 == null || addressLine1 == "" ||
  15:             city == null || city == "" || state == null || state == "" ||
  16:             postalCode == null || postalCode == "")
  17:             return null;
  18:  
  19:         if (addressLine2 != null && addressLine2 != "")
  20:             return addressLine1 + " " + addressLine2 + " " + city + ", " + state + " " + postalCode
  21:         else
  22:             return addressLine1 + " " + city + ", " + state + " " + postalCode
  23:     }
  24: }
  25:  

XML format for our serialized data (populated with data):

   1: <?xml version='1.0' encoding='UTF-8'?>
   2: <customers>
   3:     <customer id='12345'>
   4:             <firstName>FirstOne</firstName>
   5:             <lastName>FirstLastName</lastName>
   6:             <addresses>
   7:           <address id='11'>
   8:             <line-1>first address line1</line-1>
   9:             <line-2>first address line2</line-2>
  10:             <city>first-city</city>
  11:             <state>first-state</state>
  12:             <postal-code>first-postal</postal-code>
  13:           </address>
  14:           <address id='22'>
  15:             <line-1>second address line1</line-1>
  16:             <line-2>second address line2</line-2>
  17:             <city>second-city</city>
  18:             <state>second-state</state>
  19:             <postal-code>second-postal</postal-code>
  20:           </address>
  21:             </addresses>
  22:     </customer>
  23:     <customer id='67890'>
  24:             <firstName>SecondOne</firstName>
  25:             <lastName>SecondLastName</lastName>
  26:             <addresses>
  27:           <address id='33'>
  28:             <line-1>third address line1</line-1>
  29:             <line-2>third address line2</line-2>
  30:             <city>third-city</city>
  31:             <state>third-state</state>
  32:             <postal-code>third-postal</postal-code>
  33:           </address>
  34:           <address id='44'>
  35:             <line-1>fourth address line1</line-1>
  36:             <line-2>fourth address line2</line-2>
  37:             <city>fourth-city</city>
  38:             <state>fourth-state</state>
  39:             <postal-code>fourth-postal</postal-code>
  40:           </address>
  41:         </addresses>    
  42:     </customer>
  43: </customers>

In order to make things a bit closer to something you might find in a real world setting, I set this example up such that the xml element names didn’t always match the property names in the domain classes.

The following method reads the xml from a file into memory using the XmlSlurper class.  It then walks the XML graph and constructs a fully populated domain (an array list of customers with each having multiple addresses).  Here’s the sample code:

   1: public List<Customer> buildCustomersFromXMLFile() {
   2:     def customerBeans = new ArrayList()
   3:     def file = new File("D:\\temp\\AdvancedExample.xml")
   4:     def customers = new XmlSlurper().parse(file)
   5:     def customerBean
   6:     customers.children().each {customer ->
   7:         customerBean = new Customer()
   8:         def custId = customer.@id
   9:         if(custId != "")
  10:    customerBean.id = Integer.parseInt(custId.toString())
  11:         customerBean.firstName = customer.firstName
  12:         customerBean.lastName = customer.lastName
  13:  
  14:         customer.addresses.children().each {address ->
  15:             def anAddress = new Address()
  16:             def id = address.@id
  17:             if(id != "")
  18:    anAddress.id = Integer.parseInt(id.toString())
  19:             
  20:             anAddress.addressLine1 = address.'line-1'
  21:             anAddress.addressLine2 = address.'line-2'
  22:             anAddress.city = address.city
  23:             anAddress.state = address.state
  24:             anAddress.postalCode = address.'postal-code'
  25:             customerBean.addresses << anAddress
  26:         }
  27:         customerBeans << customerBean
  28:     }
  29:     return customerBeans
  30: }

This code is pretty straightforward.  The only thing I’d like to call to your attention is the extra bit of code I needed to add to deal with the id attribute.  The problem here is that strings in groovy are gstrings and not strings.  This required me to add a toString method call to the variable before I could pass it into the Integer.parseInt method.

Here’s the test harness which invokes this method and displays the values from the deserialized objects in the console window:

   1: public static void main(String[] args) {
   2:     GreetingProvider provider = new GreetingProvider();
   3:     List customers = provider.buildCustomersFromXMLFile();
   4:     Iterator iterator = customers.iterator();
   5:     while (iterator.hasNext()){
   6:         Customer customer = (Customer)iterator.next();
   7:         System.out.println("Id: " + customer.getId());
   8:         System.out.println("First Name: " + customer.getFirstName());
   9:         System.out.println("Last Name: " + customer.getLastName());
  10:         Iterator additerator = customer.getAddresses().iterator();
  11:         while (additerator.hasNext()){
  12:             Address address = (Address)additerator.next();
  13:             System.out.println("Id: " + address.getId());
  14:             System.out.println("Address: "+ address.formattedAddress());
  15:         }
  16:     }
  17: }

In my next post, we’ll take these populated domain objects and serialize them back out to XML in the same format as the xml in this example.

1 comment:

  1. Hi Travis,

    Works fine for simple stuff. In the past i've used CastorXML to good effect when it gets more complicated...but maybe it's not ever going to get that complicated?....

    Regards,
    Jez

    ReplyDelete