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) { ; } } } } } |
August 4th, 2009 - 17:39
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.
August 6th, 2009 - 09:53
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?
August 6th, 2009 - 09:59
@CHEN Cheng
yes, you can use it however you want.
September 3rd, 2009 - 01:15
Whaou!!!! très très bon!!! Merci à toi! ça va me servir!
thanks a lot!!!
October 7th, 2009 - 16:18
Thanks. Works fine.
November 20th, 2009 - 11:44
really great, thx a lot!!
December 22nd, 2009 - 23:19
nice work. doesnt seem to work with Sound objects?
January 27th, 2010 - 08:03
Thank You for this, works with loaded bitmap
February 8th, 2010 - 18:45
Thx, works great and much appreciated!
February 25th, 2010 - 16:26
Nice! Your solution made fixing a showstopping problem on our project a no-brainer. Thank you!
March 30th, 2010 - 17:07
Made my day ! Thanks !
June 2nd, 2010 - 23:50
Awesome!! Just exactly what I was looking for.. thanks a ton
June 15th, 2010 - 21:29
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
June 16th, 2010 - 08:24
Well, you cannot know what parameters to use in the constructor and you don’t have access to private properties
June 17th, 2010 - 22:28
Compliments.
A problem: methods are not cloned =\
June 21st, 2010 - 18:22
a new object of same type is created. the methods are there.
July 19th, 2010 - 15:55
This is a great class.
Sadly it does not work with sound objects
August 9th, 2010 - 19:01
This doesn’t work for MovieClip objects.
October 10th, 2010 - 23:47
Very nice…
But what do you use it for? What’s the purpose of it?
Can you explain?
Chris
December 20th, 2010 - 10:51
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
May 9th, 2011 - 09:51
Thank you for this utility, helps a lot!
July 21st, 2011 - 05:20
Thank you for this share !
July 25th, 2011 - 21:47
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.
May 31st, 2012 - 22:44
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
July 26th, 2011 - 17:28
The code helps me a lot, thank you very much
July 28th, 2011 - 11:07
Thanks for good work!
August 23rd, 2011 - 16:46
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.
August 25th, 2011 - 11:59
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?
August 25th, 2011 - 12:42
what do you mean by different ? what is different ? it doesn’t have the foto method or what exactly ?
admin recently posted..Pixel Programming
September 10th, 2012 - 10:22
How can this be used with images
September 14th, 2012 - 07:33
Thank you for this utility and Thank you for this Share, helps a lot!
September 17th, 2012 - 15:12
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());
}
}
}
September 17th, 2012 - 15:14
it’s not bullet proof because I just released it a few minutes ago. but it show the main idea.