Instant Model Binding With Reflection

Like most of the things I get excited about and share with you, this technique really doesn’t have much to it, but I love its elegance, how it works in the background and gets out of your way. While it’s really simple I think this one is a real gem, ’cause when you look at a class that uses it, it looks like magic!

Okay, so you know how when you’re writing a site or app that’s of a small to medium scale, you default to storing data in XML, and you map that XML to model classes, usually pretty directly? Or, maybe you use a configuration file for your site to load in some constants or something, and XML is a pretty easy choice for this. With E4X you can really parse through that XML quickly.

Let’s say you have a model XML file that uses attributes to store properties:

<menu>
    <icecream name="Rocky Road" chunksPerSpoonful="12" color="#E9E8CB"/>
    <icecream name="Coffee" chunksPerSpoonful="0" color="#8F6B43"/>
</menu>

Or you might decide to use child nodes:

<menu>
    <icecream>
        <name>Rocky Road</name>
        <chunksPerSpoonful>12</chunksPerSpoonful>
        <color>#E9E8CB</color>
    </icecream>
    <icecream>
        <name>Coffee</name>
        <chunksPerSpoonful>0</chunksPerSpoonful>
        <color>#8F6B43</color>
    </icecream>
</menu>

Alternately, you might use a combination of techniques if one particular attribute needs to use characters that wouldn’t be valid inside an attribute:

<menu>
    <icecream name="Rocky Road" chunksPerSpoonful="12" color="#E9E8CB">
        <description><![CDATA[If you want to get <i>nutty</i>, try this<br/>delicious flavor]]></description>
    </icecream>

    <icecream name="Coffee" chunksPerSpoonful="0" color="#8F6B43">
        <description><![CDATA[When <i>just</i> caffeine or<br/><i>just</i> sugar isn't enough, try both!]]></description>
    </icecream>
</menu>

I don’t know about you, but I would usually name the attributes in the XML precisely the same as the instance variables in my model. My model class would look so:

package com.example.models
{
    public class IceCreamModel
    {
        public var name:String;
        public var chunksPerSpoonful:Number;
        public var color:uint;
        public var description:String;
    }
}

By using public variables, I’m going to a) make sure my model can be serialized automatically by AMF and b) not worry about writing lengthy getters and setters. It also simplifies our discussion here, so bear with me.

My next step would be to write a constructor that pulls those values out of the XML. It would only be a few lines. But you know what? I have better things to do than write boilerplate code.

package com.example.models
{
    import com.yourmajesty.models.AbstractXMLPopulatedModel;
    public class IceCreamModel extends AbstractXMLPopulatedModel
    {
        public var name:String;
        public var chunksPerSpoonful:Number;
        public var color:uint;
        public var description:String;

        public function IceCreamModel(xml:XML) { super(xml); }
    }
}

Seriously, man, I’m for real here. Never ever write any XML model binding code again. Ever. Computers are here to do that monkey work for us! Kinda like Rails binds record sets directly to model objects that extend ActiveRecord, we can bind blocks of XML to model objects just by writing the fields into the model and extending AbstractXMLPopulatedModel.

AbstractXMLPopulatedModel won’t pollute your model class with any fields or methods. Its only method is the constructor, which is of course shadowed by your model’s constructor. Because of single inheritance, this implies you can’t extend any other classes in your model classes, but it’s unlikely you would do so in projects so simple that you are binding directly to XML. The automatic binding depends on your variables being public (as does AMF serialization). Note that you also get the benefit of AVM2′s fast, static, offset-based lookups of the fields in your instances; your model classes do not have to be dynamic.

Let’s see another example.

package com.example.models
{
    import com.yourmajesty.models.AbstractXMLPopulatedModel;
    public class BookModel extends AbstractXMLPopulatedModel
    {
        public var title:String;
        public var publisher:String;
        public var ISBN:String;
        public var MSRP:Number;
        public var pages:int;
        public var authors:Array;

        public function BookModel(xml:XML) { super(xml); }
    }
}
var modelxml:XML = <book
    title="ActionScript 3.0 Bible"
    publisher="Wiley"
    ISBN="978-0-470-13560-0"
    MSRP="49.99"
    pages="792"
    authors="[Roger Braunstein, Mims H. Wright, Joshua J. Noble]"
/>;

var book:BookModel = new BookModel(modelxml);
trace("$"+Math.round(book.MSRP)); //$50

All you have to do is write the fields and extend AbstractXMLPopulatedModel. That’s it!

So how does it all work? AbstractXMLPopulatedModel uses reflection to examine itself. It looks at all the variables defined, and their types, and then pulls them out of the XML, interpreting them in the proper way for the type of the variable. Take a look at the source below.

Flash Player 9′s API contains a really cool utility function called describeType() which provides super-detailed information about the passed object. It’s the ultimate method for introspection, and it returns information in XML itself. Once you have the fields defined by the XML source, and the fields defined by the class itself, it’s straightforward to map them. Following this simple logic is some basic string-to-object parsing. Nothing too fancy, and you could plunder corelib’s JSON library to add onto these capabilities if you so wished.

That’s all! It is simple, as I said before, and only saves you a little boilerplate code, but it allows you to write up models and XML in seconds, and change them at your whim. One less little pointless activity. Enjoy!

filecom.yourmajesty.models.AbstractXMLPopulatedModel View Source | Download (.as, 1k)

This entry was posted in AS3, Tips, Tricks, and Hacks. Bookmark the permalink.

7 Responses to Instant Model Binding With Reflection

  1. Tony Alves says:

    Very Nice. I believe there has been a lot of questions on how to do this on the flexcoders group.
    Thanks for sharing this little gem.

    Tony

  2. Dylan says:

    This is awesome. I’m unclear how to get the data from the model, however.

    I’ve created a test class into which I’m loading an XML file then instantiating “bookModel” and passing the XML as an argument. It compiles without errors, but I feel like I’m missing something, since I can’t yet do anything with it.

    Any thoughts?

    Thanks for an excellent post. I’m hoping to get this working asap.

  3. Hmm looks like something fundamental.

    The next thing I’m going to do is heavy testing of Your Majesty approach at the HUGE.GIS.XML file containing regular GIS data from the MapServer (usually overloaded by “boundingbox” and “geometry” tags). Let’s see how it will work. I saved the link to this page and then report results of my experiment here. If you’re interested.

    Feeling amazed.

  4. @Dylan, I didn’t make it clear enough, but each model binds to a single XML node, so to populate a set of models from an XML file, you should loop through the child nodes, creating a new model passing each node individually, f.ex.

    var root:XML = XML(someloader.data);
    for each (var node:XML in root.*)
    {
       models.push(new SomeModel(node));
    }
  5. That’s pretty sweet dude! You could apply that to cairngorm as well, except with that you would probably tie it to generating the VOs within the models, rather than the models themselves.

  6. Hey,
    Thanks a lot for sharing that class, really useful :D. I wrote a review in french about it there :
    http://www.flex-tutorial.fr/2009/04/02/flex-tips-remplir-un-model-a-partir-dun-fichier-de-configuration-xml-automatiquement/
    I also added the Boolean type in your class, just adding a case to the type switch:
    case “Boolean”:
    this[key] = value == “true” ? true : false;
    break;

    Thanks !
    Fabien

  7. jon says:

    Hi,

    Would this work with nested objects?

    thanks,

    Jon

Leave a Reply

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

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>