views:

112

answers:

2

If I've got a bunch of plain old Objects, what's the best way to map them to a bunch of Classes?

For example, if I use an HTTPService to pull in some JSON, then deserialize it, I'll have a bunch of objects which look like:

{ type: "person",
  id: 42,
  name: "David" }

And I want to turn them into instances of a Person class:

class Person {
    id:int;
    name:String;
}

Is there some standard, general way of doing this?

(also to be considered: how about some standard way of dealing with relationships between objects? For example, if the Person had an additional "spouse" field:

{ type: "person", spouse: 61, ... } // Where 61 is the ID of the spouse

)

Thanks, David

+2  A: 

There is no standard way of doing this however you could tackle it in 2 different ways.

Version 1: Constructor method

public class Person {
 public function Person(raw:Object=null) {
  if (raw != null) {
   for ( var key:String in raw ) {
    try {
     this[key] = raw[key];
    } catch (e:Error) {}
   }
   ...

This is kind of error prone. If a property named key does not exist assignment will fail and if the type doesn't match or can't be coerced automatically it will fail as well.

Version 2: flash.utils.describeType()

Much more sophisticated, gives you more control. First you have to create an instance of Person and call describeType().

var o:Object = { ... } // raw person data
var p:Person = new Person();
var typeDesc:XML = flash.utils.describeType( p );
for (var key:String in o) {
 var node:XML = typeDesc.variable.(@name==key)[0];
 if ( node != null ) {
  // Person class has a property *key*
  p[key] = o[key];
 }
}

But watch out when using getters and setters instead of public variables. You can't access them with typeDesc.variable, instead you have to use typeDesc.accessor and check if its access attribute is not writeonly.

Regarding your type key. To load a class dynamically you can do:

var clazz:Class = flash.utils.getDefinitionByName(raw.type+"");
var person:Person = Person( new clazz() );

Of course raw.type may contain Person or com.package.Person as long as your target class exists and is being compiled with the project or dynamically loaded with SWC.

Take a look at:

http://livedocs.adobe.com/flex/3/langref/flash/utils/package.html#getDefinitionByName() and http://livedocs.adobe.com/flex/3/langref/flash/utils/package.html#describeType()

Hope this helps :)

radekg
Yes, it does – thanks a lot.
David Wolever
+1  A: 

To keep things clean and less error prone, I'd use separate objects to accomplish both these tasks. For example, to handle the conversion, you might have the following object:

public class PersonFactory 
{
 function buildFromGenerics(objects:Array):Array
 {
  var a:Array = [];
  var l:int = objects.length;

  for (var i:int = 0; i < l; i++) 
  {
   var o:Object = objects[i];
   var p:Person = new Person();
   p.id = o['id'];
   p.name = o['name'];
   a[i] = p
  }

  return a;
 }
}

Moving all the logic for this step into a separate object allows you to isolate this functionality, which really is independent of the actual Person object, so has no buissness being in there.

For the second situation I'd use a separate object that holds all you Person objects in it, and supplies a interface for retrieving them, as demonstrated here:

public class PersonCollection
{
 private var _people:Array;

 public function setPeople(people:Array):void
 {
  _people = people;
 }

 public function getPersonByID(id:int):Person
 {
  var l:int = _people.length;

  for (var i:int = 0; i < l; i++) 
  {
   var p:Person = _people[i];

   if (p.id == id)
   {
    return p;
   }
  }

  return null;
 }
}

This separation helps keep your code clean, and also supplies an easy way to work with and manage it. Hope that helps.

Tyler Egeto
That does help – thanks.
David Wolever