case
ADM Blog
5Jun/090

How to clone (duplicate) an object in ActionScript 3

For a project I needed to clone an object of unknown type. And by clone I mean to create a new instance of that same type and then fill out all its properties (including getters and setters) to mirror the original object.

Thanks to a friend, I discovered the describeType function in AS3. But this alone will only take care of the copying part. To create an object of the same type as another one we use getDefinitionByName.

Although Flash reflection is pretty basic, with a little work it will do the trick.

Get the application files.

Here's the code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
< ?xml version="1.0" encoding="utf-8"?>
<mx :Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" xmlns:local="*" creationComplete="init()">
</mx><mx :Script>
 < ![CDATA[
 
     import mx.controls.Alert;
 
     private var source:DataObject = new DataObject();
     private var cloneObject:DataObject;
 
 
     private function init():void {
 
         source.name = 'John Doe';
         source.howMany = 4.5;
         source.when = new Date(0);
         source.complexProp = new DataObject();
         source.complexProp.name = 'Name in sub-object';
 
         cloneObject = UtilFunctions.clone(source) as DataObject;
 
         Alert.show("Clone:\nname = " + cloneObject.name + "\nhowMany = " + cloneObject.howMany + "\nwhen = " + cloneObject.when + "\ncomplexProp.name = " + cloneObject.complexProp.name);
     }
 
     /**
 
      * describeType will produce this (for a DataObject instance):
      *
      * <type name="DataObject" base="Object" isDynamic="false" isFinal="false" isStatic="false">
 
           <extendsclass type="Object"/>
           <accessor name="isHandicap" access="writeonly" type="Boolean" declaredBy="DataObject"/>
 
           <variable name="howMany" type="Number"/>
           <accessor name="complexProp" access="readwrite" type="DataObject" declaredBy="DataObject"/>
 
           <variable name="name" type="String"/>
           <variable name="when" type="Date"/>
 
 
      *
      * */
 
 ]]>
 
</mx>

And the UtilFunctions.as file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
package
{
 import flash.utils.describeType;
 import flash.utils.getDefinitionByName;
 import flash.utils.getQualifiedClassName;
 
 public class UtilFunctions
 {
 
 
     public static function newSibling(sourceObj:Object):* {
         if(sourceObj) {
 
             var objSibling:*;
             try {
                 var classOfSourceObj:Class = getDefinitionByName(getQualifiedClassName(sourceObj)) as Class;
                 objSibling = new classOfSourceObj();
             }
 
             catch(e:Object) {}
 
             return objSibling;
         }
         return null;
     }
 
     public static function clone(source:Object):Object {
 
         var clone:Object;
         if(source) {
             clone = newSibling(source);
 
             if(clone) {
                 copyData(source, clone);
             }
         }
 
         return clone;
     }
 
     public static function copyData(source:Object, destination:Object):void {
 
         //copies data from commonly named properties and getter/setter pairs
         if((source) && (destination)) {
 
             try {
                 var sourceInfo:XML = describeType(source);
                 var prop:XML;
 
                 for each(prop in sourceInfo.variable) {
 
                     if(destination.hasOwnProperty(prop.@name)) {
                         destination[prop.@name] = source[prop.@name];
                     }
 
                 }
 
                 for each(prop in sourceInfo.accessor) {
                     if(prop.@access == "readwrite") {
                         if(destination.hasOwnProperty(prop.@name)) {
                             destination[prop.@name] = source[prop.@name];
                         }
 
                     }
                 }
             }
             catch (err:Object) {
                 ;
             }
         }
     }
 }
}



Comments (0) Trackbacks (4)
  1. Evolvernie this is fantastic! I was having some really weird results using ByteArray.readObject()when trying to clone some objects and these classes have turned out to be an absolute life saver.

  2. Thanks a lot. I use it to set the proxyImage for DragManager.
    It’s perfect!

    BTW, which license does it apply? Can I use it freely?

  3. @CHEN Cheng
    yes, you can use it however you want.

  4. Whaou!!!! très très bon!!! Merci à toi! ça va me servir!
    thanks a lot!!!

  5. Thanks. Works fine.

  6. really great, thx a lot!!

  7. nice work. doesnt seem to work with Sound objects?

  8. Thank You for this, works with loaded bitmap

  9. Thx, works great and much appreciated!

  10. Nice! Your solution made fixing a showstopping problem on our project a no-brainer. Thank you!

  11. Awesome!! Just exactly what I was looking for.. thanks a ton

  12. I noticed that this will only work for Objects with optional or no arguments passed in the constructor. Also private properties with no setters will cause errors

  13. Compliments.

    A problem: methods are not cloned =\

  14. This is a great class.

    Sadly it does not work with sound objects

  15. This doesn’t work for MovieClip objects.

  16. Very nice…
    But what do you use it for? What’s the purpose of it?
    Can you explain?

    Chris

  17. I’ve used JSON to clone custom Objects.
    Don’t know if it works for other types though.

    import com.adobe.serialization.json.JSON;

    temp_Object = JSON.encode(original_Object)
    copied_Object = JSON.decode(temp_Object)

    Ziggy

  18. Thank you for this utility, helps a lot!

  19. Hi, nice utilFunction, however i have nested properties and it doesn’t seem to do any recursion… (the children of the children are the same).

    I have a DataObject class with an Array of NestedDataObject. When i clone, the object direct properties are not the same but the nested properties are the same.

    I look into the class and i didn’t see any recursive algorithm. I’ll try to find a workaround.

    • The code helps me to develop a recursive one. I have in my project a complex object that is an ArrayCollection with nested objects with other arrayCollections and the code i write works fine:
      Lets try?
      The full code:

      import flash.utils.describeType;
      import flash.utils.getDefinitionByName;
      import flash.utils.getQualifiedClassName;
      import mx.collections.ArrayCollection;

      public class Util
      {
      public function Util()
      {
      }

      public static function newSibling(sourceObj:Object):* {
      if(sourceObj) {

      var objSibling:*;
      try {
      var classOfSourceObj:Class = getDefinitionByName(getQualifiedClassName(sourceObj)) as Class;
      objSibling = new classOfSourceObj();
      }

      catch(e:Object) {}

      return objSibling;
      }
      return null;
      }

      public static function clone(source:Object):Object {

      var clone:Object;
      if(source) {
      clone = newSibling(source);

      if(clone) {
      copyData(source, clone);
      }
      else
      {clone = source;}
      }
      else
      {clone = source;}

      return clone;
      }

      public static function recursiveClone(source:Object):Object
      {
      var prop:XML;
      var newObject:Object = new Object();
      //var objSibling:*;
      var nomeClasse:String = new String();

      nomeClasse = getDefinitionByName(getQualifiedClassName(source)).toString() ;

      if (nomeClasse == “[class ArrayCollection]”)
      {
      var objetoClonado:Object = new Object();
      var novoArray:ArrayCollection = new ArrayCollection();

      for each(var obj:* in source)
      {
      objetoClonado = cloneRecursivo(obj);
      novoArray.addItem(objetoClonado);
      }
      newObject = novoArray;
      }
      else
      {

      newObject = clone(source);

      var sourceInfo:XML = describeType(newObject);

      for each(prop in sourceInfo.variable)
      {
      if(newObject.hasOwnProperty(prop.@name)) {
      newObject[prop.@name] = clone(source[prop.@name]);
      }
      }

      for each(prop in sourceInfo.accessor) {
      if(prop.@access == “readwrite”) {
      if(newObject.hasOwnProperty(prop.@name)) {

      try
      {
      var t:Object = source[prop.@name];
      newObject[prop.@name] = clone(source[prop.@name]);
      newObject[prop.@name] = cloneRecursivo(newObject[prop.@name]);

      }
      catch (err:Object)
      {
      ;
      }

      }

      }
      }
      }

      return newObject;
      }

      public static function copyData(source:Object, destination:Object):void {

      //copies data from commonly named properties and getter/setter pairs
      if((source) && (destination)) {

      try {
      var sourceInfo:XML = describeType(source);
      var prop:XML;

      for each(prop in sourceInfo.variable) {

      if(destination.hasOwnProperty(prop.@name)) {
      destination[prop.@name] = source[prop.@name];
      }

      }

      for each(prop in sourceInfo.accessor) {
      if(prop.@access == “readwrite”) {
      if(destination.hasOwnProperty(prop.@name)) {
      destination[prop.@name] = source[prop.@name];
      }

      }
      }
      }
      catch (err:Object) {
      ;
      }
      }
      }

      }

      How to use:

      var a:ArrayCollection = Util.recursiveClone( the object to be cloned) as ArrayCollection;

      Sorry with the identation….
      Hope this helps

  20. The code helps me a lot, thank you very much

  21. Thanks a lot!
    Found as a solution for cloning VOs. Only public properties and no methods in there – suits nice. Does it job.
    Took in my APP as ObjectUtil extension.

  22. hi, this is my code

    package
    {
    import flash.display.MovieClip;
    import flash.text.TextField;
    import flash.text.TextFormat;
    import flash.text.TextFieldAutoSize;
    import UtilFunctions;

    public class foto extends MovieClip
    {
    public function foto():void
    {
    var tf:TextField = new TextField();
    var tff:TextFormat = new TextFormat()
    tff.size = 20;
    tf.defaultTextFormat= tff;
    tf.border = true;
    tf.appendText(“params:” + “\n”);
    try
    {
    var keyStr:String;
    var valueStr:String;
    var paramObj:Object = loaderInfo.parameters;
    for (keyStr in paramObj)
    {
    valueStr = String(paramObj[keyStr]);
    tf.appendText(“\t” + keyStr + “:\t!!” + valueStr + “!!\n”);
    }
    }
    catch (error:Error)
    {
    // ignore error
    }

    tf.autoSize = TextFieldAutoSize.LEFT;
    addChild(tf);
    var tf2 = UtilFunctions.clone(tf) as TextField;
    tf2.x = 200;
    addChild(tf2);
    }
    }
    }

    I’m new on AS3, anyway it seems that the second istance is quite different from the first, any idea?

  23. How can this be used with images

  24. Thank you for this utility and Thank you for this Share, helps a lot!

  25. This is not a clone. you must implement recursion to call this a clone. Here my implementation

    public static function clone(source:Object,depth:int=0):Object {
    var className:String = getQualifiedClassName(source);
    if (isBasicType(className))
    {
    throw new Error(“can’t clone basic types”);
    }
    else if (className == “Array”)
    {
    return cloneArray(source as Array,depth);
    }
    else
    {
    return cloneObject(source,depth);
    }
    }
    /*
    * true if a basic type (int,Number,Boolean)
    * Array and Date are not basic types
    */
    private static function isBasicType(className:String):Boolean
    {
    return (className.indexOf(“.”)==-1 && className!=”Array” && className!=”Date”);
    }
    /*
    * clone a complex type
    */
    private static function cloneObject(source:Object,depth:int=0):Object {

    var clone:Object;
    if (source) {
    clone = newObject(source);

    if(clone) {
    copyProperties(source, clone,depth);
    }
    }

    return clone;
    }
    /*
    * create dynamically a new object
    */
    private static function newObject(sourceObj:Object):* {
    if(sourceObj) {
    try {
    var className:String = getQualifiedClassName(sourceObj);
    var classOfSourceObj:Class = getDefinitionByName(className) as Class;
    return new classOfSourceObj();
    }
    catch(e:Error)
    {
    trace(e.toString());
    }
    }
    return null;
    }
    /*
    * used to log recursion
    */
    private static function debugPrefix(depth:int):String
    {
    var prefix:String = “”;
    for (var j:int=0;j<depth;j++)
    {
    prefix += " ";
    }
    return prefix;
    }
    /*
    * clone each array's items regarding their types
    */
    private static function cloneArray(arraySrc:Array,depth:int=0):Array {
    var prefix:String = debugPrefix(depth);
    var arrayDst:Array = new Array();
    for (var i:int=0;i<arraySrc.length;i++)
    {
    var elementType:String = getQualifiedClassName(arraySrc[i]);
    trace(prefix+" array["+i+"]:"+elementType);
    if (isBasicType(elementType))
    {
    arrayDst.push(arraySrc[i]);
    }
    else if(elementType=="Array")
    {
    arrayDst.push(cloneArray(arraySrc[i],depth+1));
    }
    else
    {
    arrayDst.push(clone(arraySrc[i],depth+1));
    }
    }
    return arrayDst;
    }
    /*
    * clone each object properties regarding their types
    */
    private static function copyProperties(source:Object, destination:Object,depth:int=0):void {
    var propType:String = "";
    var prefix:String = debugPrefix(depth);

    if((source) && (destination)) {

    try {
    var sourceInfo:XML = describeType(source);
    var prop:XML;

    trace(prefix+"Clone "+sourceInfo.@name);
    for each(prop in sourceInfo.accessor) {
    propType = prop.@type;
    if(prop.@access == "readwrite") {
    if(destination.hasOwnProperty(prop.@name)) {
    trace(prefix+" property "+prop.@name+":"+propType);
    if (isBasicType(propType)) // basic types
    {
    destination[prop.@name] = source[prop.@name];
    }
    else if( propType=="Array")
    {
    var arraySrc:Array = source[prop.@name] as Array;
    destination[prop.@name] = cloneArray(arraySrc,depth+1);
    }
    else
    {
    destination[prop.@name] = clone(source[prop.@name],depth+1);
    }
    }
    }
    }
    }
    catch (err:Error) {
    trace(err.toString());
    }
    }
    }

  26. it’s not bullet proof because I just released it a few minutes ago. but it show the main idea.


Reply

( Cancel )

CommentLuv badge

*