How to create a generic function for parsing an XML into an Array Collection of Objects of a certain type

The Problem

Over time, I found that it is more productive and less error prone (at least for me :D) to work with structures of data that are rather collections of typed Objects than collections of Objects or XML. Here are some advantages when you work with typed objects: gets support from Flex Builder tool (code completion, compile time checking), the code is easier to read and maintain, and somehow I think is more elegant.

Back to earth now, let’s say I have a Flex application that reads data from a server and format of the data is XML. For example, my Flex application receives some data like this:

<?xml version="1.0" encoding="utf-8"?>
<root>
    <person>
        <name>John</name>
        <age>25</age>
    </person>
    <person>
        <name>Mike</name>
        <age>27</age>
    </person>
    <person>
        <name>Michele</name>
        <age>22</age>
    </person>
    <person>
        <name>Julia</name>
        <age>29</age>
    </person>
</root>

I could use this data like an XML object in my application (for example if I use a HTTPService to bring them, I can set the resultFormat property to “XML”), or I can create an ActionScript class VOPerson:

package org.example {

    public class VOPerson {

        public var name:String;
        public var age:int;

    }
}

And I can transform this XML in an Array or Array Collection of VOPerson.

In an AIR application you can save on disk any object you might have. Suppose I have an Array Collection of VOPerson, I can use AIR API to save this object on disk. But, there is an issue: when I read the object from disk back into my application, the type of objects from Array Collection is lost. So instead of an Array Collection of VOPerson, I will have an Array Collection of Objects. This could be a problem for me, as my methods expect a VOPerson object and not a generic Object.

The Solution

The solution is to create a “generic” function that knows how to transform a collection (could be an Array, an Array Collection or a XMLList) of generic objects into an Array Collection of typed Objects. I could use this function when I read serialized objects from disk in AIR or when I use HTTPService to get XML from server in a Flex application. In order to have a simple solution I need to use the same names for the XML nodes and my typed object properties.

Let’s start with the HTTPService and XML code. This service has a property xmlDecode that can be used to set a function that is called right before the result handler gets called. This function must return an Object, and the return object will be passed in the event. result property to the result handler. The XML received from server is passed as an argument to the function, as XMLNode object. Pay attention that this function is called only when you set the resultFormat property to “object”.

So let’s take a look at how the code might look:

<mx:HTTPService id="myService" url="http://corlan.org/downloads/examples/get_data.html"
        method="POST" resultFormat="object" showBusyCursor="true"
        xmlDecode="serializeXMLToVO"
        result="resultListener(event)" fault="faultListener(event)"/>

Let’s define the three functions I set up in the HTTPService object (resultListener, faultListener, serializeXMLToVO):

private function resultListener(event:ResultEvent):void {
    myCollection = event.result as ArrayCollection;
}

private function faultListener(event:FaultEvent):void {
    Alert.show(event.fault.faultDetail, "Error");
}

private function serializeXMLToVO(node:XMLNode):Object {
    var xml:XML = new XML(node);     //make an XML object to be able to work with E4X
    var list:XMLList = xml..person; //get a XML List with all the nodes person
    //call the generic function that knows to make from a generic collection of dynamic objects
    //an array collection of value objects
    return serializeToVO(list, VOPerson);
}

As you can see in the resultListener I’ve just saved the event.result cast as ArrayCollection to myCollection variable (I use this variable as data provider for my data grid). So although the server sends me an XML, the result is translated to a proper object. All this transformation is done by the serializeXMLToVO function (this function is called by HTTPService to process the result and after this the ResultEvent is dispatched). In serializeXMLToVO first I create an XML object out of XMLNode (it is far more easy to work with E4X to get my data out of XMLNode) and then I create an XMLList with all the person nodes. The next step is to call my generic function (serializeToVO) and pass to this function my XMLList and the class I want to be used for storing one person node (VOPerson is my class in this case). This function will walk through and will do the “dirty” job. I wanted to use a generic function so I can reuse it whenever I have similar needs and also I can pass any object type I want to use.

Let’s take a look at this function:

private function serializeToVO(collection:Object, claz:Class):ArrayCollection {
    var arr:ArrayCollection = new ArrayCollection();
    //use reflection to find all the members from our claz object
    var tmp:XML = describeType(new claz());
    //save just the variables into am XML List using an E4X expression
    //(I want to get all the atributes "name" from all the nodes "variable")
    var properties:XMLList = tmp..variable.@name;
    //iterate our collection
    for each (var item:Object in collection) {
        var temp:Object = new claz();
        //iterate our properties and use the values from the current item 
        //to initializa our value object
        for each(var member:Object in properties) {
            temp[member] = item[member];
        }
        arr.addItem(temp); //add the item to collection
    }
    return arr; //return the array collection
}

Let’s take a look at the arguments: we have an Object (collection) which in fact should be an instance of XMLList, Array or ArrayCollection. The other one is a Class object, this is the type I want to be used for saving an item from the collection. Because I don’t know what properties might have the claz object, I need to use reflection and find what members I have in the claz:

var tmp:XML = describeType(new claz());
var properties:XMLList = tmp..variable.@name;

flash.utils.describeType does this reflection; it returns an XML with all the information I need. From this XML using an E4X expression I can create an XMLList with the members I am looking for. Now I need to loop through my collection and for each item I need to loop through my list of members and read the value from the item and write the value into my typed object (this works because I have the same names both for XML and my type objects; I have a node name in the person, and I have a property name in VOPerson). In ActionScript you don’t have iterators, or you can say you have but the iterator is the construct for each (item:object in collection). So in this way I can iterate on any Array, XMLList or ArrayCollection or on an Object.

That’s it for the HTTPService. The code for writing and reading the array collection to disk is much simpler. I have a saveData function and a readData function and in readData I make use again of my function serializeToVO. This time, on the ArrayCollection retrieved from disk.

private function saveData():void {
    var file:File = File.applicationStorageDirectory.resolvePath(fileName);
    if (file.exists)
        file.deleteFile();
    var fileStream:FileStream = new FileStream();
    fileStream.open(file, FileMode.WRITE);
    fileStream.writeObject(myCollection);
    fileStream.close();
    Alert.show("the data was saved in " + file.nativePath, "File Path");
}

private function readData():void {
    var file:File = File.applicationStorageDirectory.resolvePath(fileName);
    if (!file.exists)
        return;
    var fileStream:FileStream = new FileStream();
    fileStream.open(file, FileMode.READ);
    var myObject:ArrayCollection = fileStream.readObject() as ArrayCollection;
    fileStream.close();
    myCollection = serializeToVO(myObject, VOPerson);
}

Sample Code

As usually :), I created an AIR project with all the code. You can download it from here and you can import it in Flex Builder using Import > Flex Builder > Flex Project. When you run the application, you will see a data grid and four buttons:

  • “Get Data” – retrieves and displays the data from server in the Data Grid
  • “Save Data Locally” – save the Array Collection to a file on disk
  • “Clear data from server” – makes the Array Collection used to store the data from server null
  • “Read Data Saved Locally” – retrieves the data from the disk and stores in the Array Collection used as data provider for data grid

My AIR sample app

If you want to have a quick view on the code:

<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
    <mx:Script>
        <![CDATA[
            import flash.utils.describeType;
            import mx.collections.IList;
            import mx.controls.List;
            import org.example.VOPerson;
            import mx.collections.ArrayCollection;
            import mx.controls.Alert;
            import mx.rpc.events.FaultEvent;
            import mx.rpc.events.ResultEvent;

            [Bindable]
            private var myCollection:ArrayCollection; //this is collection used to store the data

            private var fileName:String = "myObject"; //this is file name used to saved locally the data

            /**
             * The result listener for the HTTPService. 
             * Saves the result into <code>myCollection</code> array collection
             * @param event
             */
            private function resultListener(event:ResultEvent):void {
                myCollection = event.result as ArrayCollection;
            }

            /**
             * Fault listener for HTTPService. It's just displaying the error message
             * @param event
             */
            private function faultListener(event:FaultEvent):void {
                Alert.show(event.fault.faultDetail, "Error");
            }

            /**
             * This function is called by the HTTPService when the result is available
             * and before the listener for result gets called. Thus I can parse the XML
             * returned by the server into an Array Collection of value objects VOPerson
             * @param node
             * @return an Array collection of VOPerson
             */
            private function serializeXMLToVO(node:XMLNode):Object {
                var xml:XML = new XML(node);     //make an XML object to be able to work with E4X
                var list:XMLList = xml..person; //get a XML List with all the nodes person
                //call the generic function that knows to make from a generic collection of dynamic objects
                //an array collection of value objects
                return serializeToVO(list, VOPerson);
            }

            /**
             * This is a generic function that can transform a collection of dynamic objects into
             * an array collection of a certain object type (the given <code>claz</code>)
             * @param collection to be parsed; could be an array collection or an XMLList or an array
             * @param claz to be used for storing one item
             * @return an array collection of <code>claz</code>
             */
            private function serializeToVO(collection:Object, claz:Class):ArrayCollection {
                var arr:ArrayCollection = new ArrayCollection();
                //use reflection to find all the members from our claz object
                var tmp:XML = describeType(new claz());
                //save just the variables into am XML List using an E4X expression
                //(I want to get all the atributes "name" from all the nodes "variable")
                var properties:XMLList = tmp..variable.@name;
                //iterate our collection
                for each (var item:Object in collection) {
                    var temp:Object = new claz();
                    //iterate our properties and use the values from the current item 
                    //to initializa our value object
                    for each(var member:Object in properties) {
                        temp[member] = item[member];
                    }
                    arr.addItem(temp); //add the item to collection
                }
                return arr; //return the array collection
            }

            /**
             * Save the value of <code>myCollection</code> into a file inside the
             * local application storage directory
             */
            private function saveData():void {
                //create the file to store the data
                var file:File = File.applicationStorageDirectory.resolvePath(fileName);
                if (file.exists)
                    file.deleteFile();
                //use a File Stream to write to file    
                var fileStream:FileStream = new FileStream();
                fileStream.open(file, FileMode.WRITE);
                //write the data as an object to file
                fileStream.writeObject(myCollection);
                fileStream.close();
                //display the path to the file I wrote it on disk
                Alert.show("the data was saved in " + file.nativePath, "File Path");
            }

            /**
             * Read the serialized object on the hard-disk and transform it into 
             * an array collection of VOPerson
             * Save array collection of VOPerson into the local <code>myCollection</code>
             * variable
             */
            private function readData():void {
                //get the file use
                var file:File = File.applicationStorageDirectory.resolvePath(fileName);
                if (!file.exists)
                    return;
                var fileStream:FileStream = new FileStream();
                fileStream.open(file, FileMode.READ);
                var myObject:ArrayCollection = fileStream.readObject() as ArrayCollection;
                fileStream.close();
                //use my generic serializing function to get an array collection of VOPerson
                myCollection = serializeToVO(myObject, VOPerson);
            }
        ]]>
    </mx:Script>

    <mx:HTTPService id="myService" url="http://corlan.org/downloads/examples/get_data.html"
        method="POST" resultFormat="object" showBusyCursor="true"
        xmlDecode="serializeXMLToVO"
        result="resultListener(event)" fault="faultListener(event)"/>

    <mx:VBox left="25" top="25">
        <mx:Button label="Get Data" click="myService.send()"/>
        <mx:Button label="Save Data Locally" click="saveData()" />
        <mx:Button label="Clear data from server" click="myCollection = new ArrayCollection()"/>
        <mx:Button label="Read Data Saved Locally" click="readData()" />

        <mx:DataGrid id="myGrid" dataProvider="{myCollection}"/>
    </mx:VBox>
</mx:WindowedApplication>

I am curious if you run into the same issues and what solution did you find for.

17 thoughts on “How to create a generic function for parsing an XML into an Array Collection of Objects of a certain type

  1. Are there any advantages over using an ArrayCollection over an XMLListCollection?

    I’m pulling XML data from a static file using an XMLList… just wondering if there’s any advantage to parsing the data into an ArrayCollection (it’s fairly complex nested XML).

    Sherm

  2. No, if you put in these terms, there are no advantages. But, what I found while working on Flex applications that grows and need maintenance is that is more simple and cleaner to work with typed objects instead of proxy objects. That is to wrap the data rows in an value object class. Thus I can beneffit of Flex Builder code completion and compile time errors.

    Well ideally it would be to send the data from server directly as AS objects (using remoteing and AMF). But sometimes you have XML on server and thus my function :)

  3. Mihai, thanks for the tute.
    One question…i’m not pulling an XML file from a server…i have an ArrayCollection which is saved to the desktop which acts as the dataprovider for my datagrid.

    I have modified the serialToVO function b/c i’m not working with any XML…all i did was remove the 2nd for loop…but i keep getting an error…any advice would be great. Thanks.

    private function serialToVo (collection:Object, claz:Class):ArrayCollection

    {
    var arr:ArrayCollection = new ArrayCollection();

    for each (var item:Object in collection) {

    var temp:Object = new claz()
    temp = item;
    arr.addItem(temp);

    }

  4. @Jack

    I think the problem is that item is not of type claz. Thus, when you do:
    temp = item; you get the warning because the two variables have different types.

    I wrote an article on saving ArrayCollection to disk in AIR. And what is happening, is that the type of the items is lost when you read the array from disk. You could use my function I am giving in this post.

    By the way, you can see for yourself what is happening if you launch the project in debug mode and you place a break point where you assign the item to the temp. And check what types they have.

  5. Pingback: Storing data locally in AIR : Mihai CORLAN

  6. Pingback: Recent Links Tagged With "e4x" - JabberTags

  7. This was a very helpful post, I was searching the net for an example of how to store and retrieve an ArrayCollection from the EncryptedLocalStore. I used your example and modified it to store my value objects and serialize them again when I pull the ArrayCollection from the storage. Thanks for the post man, it was a big help on my project!

    -Mr

  8. Pingback: Storing an ArrayCollection in the EncryptedLocalStore — Thanks, Mister!

  9. Thanks for yr post. I’m searching a lot this topic to compare my work/problem. I have done an app that save VObjects locally but the structure to save is a hierarchy of VOs. I’ve tagged all the custom classes with [RemoteClass (alias=”com.ekr.project.vo.XnameClass”)] so i use the AMF de/serialization to recognize the object types.

    But i’ve found it’s _NOT_ a stable process!
    Sometimes happen that you save data and then when you re-open it some piece of data it’s lost!

    Do u think it’s can be the right way to save data locally? I’m try to discover and fix this bug and i’m very interested about this topic. Soon i’ll open a my blog so i can share some code… and problems! Thanks for yr contribute.

  10. @Paolo Carraro

    When you have some code to share, please let me know because I’m really curios about it.

  11. Does your example work with a boolean value for a checkbox inside a datagrid? I’m not having any luck.

  12. Just using your example, if I add a ‘public var saved_item:Boolean;’ to the VOPerson class and placing a true to my XML file the returned data isn’t correct. I’ve tried to make the saved_item bindable or non-bindable without any luck.

  13. Hi,

    I am pulling an XML file from a server, and i want to serialise the”tw:twproject” into TwprojectVO, but i don’t know which types of properties i can make in the TwprojectVO class. here is an example of my XML file which can change(not static):

    can you give me an idea.

    Sincerly,
    Celine

  14. Pingback: Storing an ArrayCollection in the EncryptedLocalStore

  15. Pingback: Storing an ArrayCollection in the EncryptedLocalStore « Thanks, Mister!

Leave a Reply

Your email address will not be published. Required fields are marked *