4 Replies Latest reply on Jan 11, 2011 7:22 AM by Claudiu Ursica

    Transforming data returned from a JSON service into my actual model objects

    JoshBeall Level 1

      Hi All,

       

      I've got some model object in my application that have definite types (e.g., Person, Address, Membership, etc.).

       

      I'm working on the service that will handle communication from the Flex client and the server.  Now, when I get a JSON object back from the server, it is going to have a different type than my model objects--either it will just be a dynamic object with properties mapping to all the data in the JSON string, or I might use Flex's tools to create a strongly typed result.  Either way, the result of the service call will be different than the type of my model classes in the Flex app.

       

      What is the best way to suck in the data from the results of my service call and populate my model objects?  Do I have to go field by field, like this:

       

      model.firstname = result.firstname;

      model.lastname = result.lastname;

      model.email = result.email;

       

      And so on?  Or, is there a way in ActionScript to say "every field on the result object, assign to the corresponding field on the model object?"

       

        -Josh

        • 1. Re: Transforming data returned from a JSON service into my actual model objects
          Claudiu Ursica Level 4

          I used this a while ago to translate objects. Found it on some blog don't

          remeber who deserves the credit for it ...

           

          package

          {

              import mx.collections.ArrayCollection;

           

              public class ObjectTranslator           

              {

                  import mx.utils.ArrayUtil;

                  import flash.utils.*;

                  import mx.core.DeferredInstanceFromClass;

                   

                  public static function translateArrayObjects(arrayOfObjects:Array,

          translateToClass:Class, upperCaseFromVar:Boolean=false):Array

           

                  {

                      // describeType describes a class and all of its properties

                      var classInfo:XML = describeType(translateToClass);

                       

                      // get the class name, in order to instantiate more classes

                      var className:String = classInfo.@name;

                      var i:Number = new Number();

                      var returnArray:Array = new Array();

                       

                      // get a reference to the class to create new classes

                      var classRef:Class = Class(getDefinitionByName(className ));

                      var tempObj:Object = new classRef();

                       

                      if (arrayOfObjects != null)   

                      {

                          for (i=0; i <arrayOfObjects.length; i++)   

                          {

                              // for each new object, create a new class of the class type

          that was passed in.

                              tempObj = new classRef();

                              // send it off to translateObject function. send it the

          class to populate

                              returnArray.push(translateObject(arrayOfObjects[i], tempObj,

          classInfo, upperCaseFromVar));

                          }

                      }

                      return returnArray;  // return the translated array

                  }

                   

                   

                  public static function translateObject(translateFrom:Object,

          translateTo:Object, classInfo:XML = null, upperCaseFromVar:Boolean =

          false):Object

                  {

                      var vType:String;

                      var vName:String;

                      var fromName:String;

                       

                      // if class information has already been sent in, we don't need to

          re-fetch it.

                      if (classInfo == null)

                      {

                          classInfo = describeType(translateTo);

                      }

                       

                      for each (var v:XML in classInfo..variable)

                      {

                          try    

                          {

                              // xml values don't exactly match the variable name unless

          it knows for sure it's a string

                              vName = v.@name;

                              vType = v.@type;

                           

                              if (upperCaseFromVar)   

                              {

                                  fromName = vName.toUpperCase();

                              }

                              else

                              {

                                  fromName = vName;

                              }

                               

                              // check to see if the untyped object has a property that

          matches the typed property

                              // if it does, then translate it.  if not, skip it.

                              // this is done to maintain integrity with the application

          and the internal classes.

                              // internal classes are static (unless they've been made

          dynamic) an error will result if

                              // and unknown variable is set in it.

                              if (translateFrom.hasOwnProperty(vName) &&

          translateFrom[vName] != null)    

           

                              {

                                  // Only need to check for a few types here. variables

          should come across

                                  // in a limited number of base object types, ie numbers,

          ints, dates, strings, etc.

                                  // anything else is assumed to be a class and will be

          sent for translation as well.

                                  // the one case that is not dealt with here is when an

          array of objects underneath

                                  // an array of objects.  This can be theoretically set

          up, but there's no clear

                                  // an array of objects has to be a special type of

          array, which in most cases only limits

                                  // the objects that can be store in it to a specific

          class.  the simplest way around

                                  // this is to send back a simple array of untyped

          objects which can then be translated

                                  // at the point that they are needed.

                                   

                                  switch (vType) 

                                  {

                                      case "Array":

                                          // in beta 3 SOAP sequences were typed as arrays

          by FLEX.

                                          if (translateFrom[vName] is ArrayCollection)    

           

                                          {

                                              translateTo[vName] =

          translateFrom[fromName].source;

                                          }

                                          else if (translateFrom[fromName] is Array)    

               

           

                                          {

                                              translateTo[vName] =

          translateFrom[fromName];

                                          }

                                          break;

                                      case "mx.collections::ArrayCollection":

                                          // in the GA release it looks like SOAP

          sequences are

                                          // typed as arrayCollections.  Support has been

          left in for both

                                          // types... just in case, so this function can

          be used for both types.

                                          if (translateFrom[fromName] is ArrayCollection)

               

           

                                          {

                                              translateTo[vName] =

          translateFrom[fromName];

                                          }

                                          else if (translateFrom[fromName] is Array)    

               

           

                                          {

                                              translateTo[vName].source =

          translateFrom[fromName];

                                          }

                                          break;

                                      case "Number" :

                                          // sometimes numbers come across as a

          complexString.  this can

                                          // cause a mess at runtime if there is a need to

          check against a

                                          // simple number type.

                                          translateTo[vName] = new

          Number(translateFrom[fromName]);

                                          break;

                                      case "int" :

                                          // ditto for ints

                                          translateTo[vName] = new

          int(translateFrom[fromName]);

                                          break;

                                      case "uint" :

                                          // ditto for uints

                                          translateTo[vName] = new

          uint(translateFrom[fromName]);

                                          break;

                                      case "Date":

                                          // ditto for dates.  what's nice about this

          function is that with a little

                                          // work, dates can automatically be transfered

          from a UTC format or a string

                                          // format (used sometimes for easy transport) 

          into an actual date format

                                          // which should be easier to use.

                                           

                                          if (translateFrom[fromName] is Date)

                                          {

                                              translateTo[vName] =

          translateFrom[fromName];

                                          }

                                          else if (translateFrom[fromName] is String)

                                          {

                                              translateTo[vName] =

          parseDateFromString(translateFrom[fromName]);

                                          }

                                          else if (translateFrom[fromName] is Number ||

          translateFrom[fromName] is int ||    translateFrom[fromName] is uint)

           

                                          {

                                              translateTo[vName] =

          parseUtcDate(translateFrom[fromName]);

                                          }

                                          break;

                                      case "Boolean":

                                          // Booleans get typed as a complexString

          sometimes too.

                                          translateTo[vName] =

          getBooleanValue(translateFrom[fromName]);

                                          break;

                                      case "String":

                                          translateTo[vName] = new

          String(translateFrom[fromName]);

                                          break;

                                      case "Object":

                                          translateTo[vName] = translateFrom[fromName];

                                          break;

                                      default:

                                          // assuming that anything that makes it here is

          a sub class...

                                          // send it to the this function again to decode

          it...

                                          var definition:Class =

          getDefinitionByName(vType.replace(/::/, ".")) as Class;

                                          translateTo[vName] =

          translateObject(translateFrom[fromName], new definition());

                                          break;

                                  }

                              }

                          }

                          catch (e:Error) 

                          {

                              // do nothing // we will loose data, but our app will stay

          in tact;

                              //if (translateFrom.hasOwnProperty(v.@name))     {

                              //    translateTo[v.@name] = translateFrom[v.@name];

                              //}

                              trace("an error occured in ObjectTranslator. The following

          fields were not captured correctly - to:" + vName + "  from:" + fromName);

                              trace(translateTo[vName]);

                              trace(translateFrom[fromName]);

                          }

                           

                      }

                       

                      return translateTo;

                  }

                   

                  public static function

          translateArrayCollection(collection:ArrayCollection, translateToClass:Class,

          upperCaseFromVar:Boolean=false):ArrayCollection    

           

                  {

                      // this is a convenience function.  in the GA release of FLEX 2,

          objects are now coming back as

                      // array collections.  if an array collection is expected, this

          function will check to make sure

                      // that it is not null.  it returns a completely new collection,

          which may or may not be what is desired.

                      var returnCollection:ArrayCollection = new ArrayCollection();

                      if (collection != null)    

                      {

                          if (collection.length > 0)   

                          {

                              returnCollection.source =

          translateArrayObjects(collection.source, translateToClass, upperCaseFromVar);

                          }

                      }

                      return returnCollection;

                  }

                   

                  public static function

          translateArrayCollectionAsArray(collection:ArrayCollection,

          translateToClass:Class, upperCaseFromVar:Boolean=false):Array        

           

                  {

                      // this is a convenience function.  in the GA release of FLEX 2,

          objects are now coming back as

                      // array collections.  if an array collection is expected, this

          function will check to make sure

                      // that it is not null.  it returns a completely new Array, which

          may or may not be what is desired

                      var returnArray:Array = new Array;

                      if (collection != null)    

                      {

                          if (collection.length > 0 )   

                          {

                              returnArray =

          translateArrayObjects(collection.source,translateToClass,upperCaseFromVar);

                          }

                      }

                      return returnArray;

                  }

                   

                  public static function parseUtcDate(utcNumber:Number):Date   

                  {

                      var returnDate:Date;

                      // there should be a minimum length here for a valid utc Date

                      if (utcNumber.toString().length > 6) 

                      {

                          returnDate = new Date;

                          returnDate.setTime(utcNumber);

                      }

                      return returnDate;

                  }

                   

                  public static function parseDateFromString(dateString:String):Date    

                  {

                      var returnDate:Date;

                      // check to see if we have a utc date as a string

                      // guessing that dateString should be at least 6 chars long in order

          to be

                      // considered a viable date.... this could be a problem though,

          because theoretically,

                      // a time of zero should still be valid.

                      if (dateString.length > 6 && parseInt(dateString).toString().length

          6)    

           

                      {

                          // we probaly have a utc date here

                          returnDate = parseUtcDate(parseInt(dateString));

                      }

                      else

                      {

                          //  we didn't get a utc date so let's try to parse it...

                          returnDate = parseUtcDate(Date.parse(dateString));

                      }

                       

                      return returnDate;

                  }

                   

                  public static function getBooleanValue(bValue:Object):Boolean       

                  {

                      var returnBoolean:Boolean = false;

                      var stringBoolean:String = bValue.toString().toLowerCase();

                      if (stringBoolean == "true" || stringBoolean == "yes" ||

          stringBoolean == "1" || stringBoolean == "-1")   

           

                      {

                          returnBoolean = true;

                      }

                      return returnBoolean;

                  }

                   

                  public static function clone(source:Object):*

                  {

                      var myBA:ByteArray = new ByteArray();

                      myBA.writeObject(source);

                      myBA.position = 0;

                      return(myBA.readObject());

                  }

                   

              }

          }

           

           

          usage:

           

          var response:CustomObjectResponse =

          ObjectTranslator.translateObject(event.result, new CustomObjectResponse()) as

          CustomObjectResponse;

           

          HTH,

          C

          • 2. Re: Transforming data returned from a JSON service into my actual model objects
            JoshBeall Level 1

            What is this line doing?

             

            for each (var v:XML in classInfo..variable)

             

            I looked through the describeType XML and I can't find "variable" anywhere, so I'm not sure what that's supposed to do.

             

            I do know that when the Flash player gets to this line, it skips the entire for each loop, I'm assuming because it can't find an XMLList when it evaluates the expression "classInfo..variable"...? If I put a watch on that expression, I get an empty XMLList.

             

              -Josh

            • 3. Re: Transforming data returned from a JSON service into my actual model objects
              JoshBeall Level 1

              Maybe the describeType XML has changed since you wrote that?  I tried changing it to classInfo..accessor and that seemed to do the trick.

               

                -Josh

              • 4. Re: Transforming data returned from a JSON service into my actual model objects
                Claudiu Ursica Level 4

                I haven;t used that in the last 6 months or even more. When I used it was with

                flex3. I put up a quick example which I have attached to the email. Hope the

                attachment will upload if not I can email it to you. Your issue might be due to

                the fact that you use getters setters instead of public variables for your value

                objects. Check the example and you will see that the variable show in the xml

                instead of the accessor. Feel free to enhance it I did not write it in the first

                place. Also if you look in there the code does not catters for the ArrayList

                type.

                 

                If/when have the time I might be updating it.

                 

                C