Serialize Anything
The ByteArray
class, introduced in Flash Player 9, has a pair of very powerful functions alongside all the usual ones like writeInt
. It actually allows you to read and write AS3 objects and provides an easy, fast, and compact way to serialize anything without writing any code. Today’s article explores shows you how to harness the power of these functions to improve your data serialization and deserialization.
First, let’s start off with a class we’d like to serialize/deserialize:
class Person { public var first:String; public var last:String; public var age:int; }
This is a simple class as it only contains public variables with built-in types. Still, let’s serialize it:
var person:Person = new Person(); person.first = "William"; person.last = "Shatner"; person.age = 80; var bytes:ByteArray = new ByteArray(); bytes.writeObject(person);
See how easy that was? The deserialization is just as easy:
bytes.position = 0; // back to where we wrote the object var obj:Object = bytes.readObject(); trace(obj.first + " " + obj.last + " is " + obj.age); // William Shatner is 80
The downside here is that the object we get back is not typed: we get a plain Object
. To get around this, use flash.net.registerClassAlias
to give it a name:
registerClassAlias("Person", Person); // Alias the Person class to "Person" var person:Person = new Person(); person.first = "William"; person.last = "Shatner"; person.age = 80; var bytes:ByteArray = new ByteArray(); bytes.writeObject(person);
Now when we read it back, we’ll get a typed object:
bytes.position = 0; // back to where we wrote the object var ws:Person = bytes.readObject() as Person; trace(ws.first + " " + ws.last + " is " + ws.age); // William Shatner is 80
This is all very simple and very powerful. There’s no need to write a bunch of AS3 code to serialize and deserialize to your own binary format or XML or JSON (unless you’re looking for an interchange format) when you can just use readObject
and writeObject
.
Now let’s throw in a private variable and see what happens:
class Person { public var first:String; public var last:String; private var __secret:String; public var age:int; public function setSecret(secret:String): void { __secret = secret; } public function getSecret(): String { return __secret; } } registerClassAlias("Person", Person); var person:Person = new Person(); person.first = "William"; person.last = "Shatner"; person.age = 80; person.setSecret("I'm a Canadian"); var bytes:ByteArray = new ByteArray(); bytes.writeObject(person); var ws:Person = bytes.readObject() as Person; trace(ws.first + "'s secret is: " + ws.getSecret());
Unfortunately, this last line won’t work and you’ll get:
William's secret is: null
The reason for this is that private variables are not serialized… usually. You can get around this limitation by providing a getter and setter:
class Person { public var first:String; public var last:String; private var __secret:String; public var age:int; public function set secret(secret:String): void { __secret = secret; } public function get secret(): String { return __secret; } }
Now we’ll get what we expected:
William's secret is: I'm a Canadian
You can easily write out collections of objects, too:
var person:Person = new Person(); person.first = "William"; person.last = "Shatner"; person.age = 80; person.secret = "I'm a Canadian"; var person2:Person = new Person(); person2.first = "George"; person2.last = "Takei"; person2.age = 74; person2.secret = "I can speak Spanish"; // Serialize just like any other object bytes.writeObject(new <Person>[person, person2]); // Deserialize just like any other object var people:Vector.<Person> = bytes.readObject() as Vector.<Person>; for each (var p:Person in people) { trace(p.first + " " + p.last + " is " + p.age + ". Secret: " + p.secret); }
And you get:
William Shatner is 80. Secret: I'm a Canadian George Takei is 74. Secret: I can speak Spanish
These functions even support inheritance:
class Entity { private static var __nextID:int = 100; public var id:int = __nextID++; } class Person extends Entity { public var first:String; public var last:String; private var __secret:String; public var age:int; public function set secret(secret:String): void { __secret = secret; } public function get secret(): String { return __secret; } } var person:Person = new Person(); person.first = "William"; person.last = "Shatner"; person.age = 80; person.setSecret("I'm a Canadian"); var person2:Person = new Person(); person2.first = "George"; person2.last = "Takei"; person2.age = 74; person2.setSecret("I can speak Spanish"); // Serialize just like any other object bytes.writeObject(new <Person>[person, person2]); // Deserialize just like any other object var people:Vector.<Person> = bytes.readObject() as Vector.<Person>; for each (var p:Person in people) { trace(p.first + " " + p.last + "(ID: " + p.id + ") is " + p.age + ". Secret: " + p.secret); }
With no change to how we serialize or deserialize, we get what we expect:
William Shatner (ID: 100) is 80. Secret: I'm a Canadian George Takei (ID: 101) is 74. Secret: I can speak Spanish
What we have here is a simple way to serialize any AS3 class to a ByteArray
and deserialize it back into an object. But how fast is it compared to, say, XML? Let’s look at a quick performance test:
package { import flash.display.*; import flash.utils.*; import flash.text.*; import flash.net.*; public class SerializeAnything extends Sprite { private var __logger:TextField = new TextField(); private function log(msg:*): void { __logger.appendText(msg + "\n"); } private function row(...cols): void { __logger.appendText(cols.join(",")+"\n"); } public function SerializeAnything() { stage.align = StageAlign.TOP_LEFT; stage.scaleMode = StageScaleMode.NO_SCALE; var logger:TextField = __logger; logger.autoSize = TextFieldAutoSize.LEFT; addChild(logger); var beforeTime:int; var afterTime:int; var i:int; var SIZE:int = 100000; registerClassAlias("Person", Person); var people:Vector.<Person> = new Vector.<Person>(SIZE); for (i = 0; i < SIZE; ++i) { var person:Person = new Person(); person.secret = "I'm a Canadian"; person.first = "William"; person.last = "Shatner"; person.age = 80; people[i] = person; } row("Operation", "Time"); beforeTime = getTimer(); var ba:ByteArray = new ByteArray(); ba.writeObject(people); afterTime = getTimer(); row("ByteArray serialize", (afterTime-beforeTime)); beforeTime = getTimer(); var xmlString:String = "<people>"; for (i = 0; i < SIZE; ++i) { person = people[i]; xmlString += "<person first=\"" + person.first + "\" last=\"" + person.last + "\" age=\"" + person.age + "\" secret=\"" + person.secret + "\" />"; } xmlString += "</people>"; var xml:XML = new XML(xmlString); afterTime = getTimer(); row("XML serialize", (afterTime-beforeTime)); beforeTime = getTimer(); ba.position = 0; people = ba.readObject() as Vector.<Person>; afterTime = getTimer(); row("ByteArray deserialize", (afterTime-beforeTime)); beforeTime = getTimer(); people = new Vector.<Person>(); for each (var personNode:XML in xml.person) { person = new Person(); person.first = personNode.@first; person.last = personNode.@last; person.age = personNode.@age; person.secret = personNode.@secret; people.push(person); } afterTime = getTimer(); row("XML deserialize", (afterTime-beforeTime)); row(); row("Format", "Size"); row("ByteArray", ba.length); row("XML", xmlString.length); } } } class Person { public var first:String; public var last:String; private var __secret:String; public var age:int; public function set secret(secret:String): void { __secret = secret; } public function get secret(): String { return __secret; } }
I ran this performance test with the following environment:
- Flex SDK (MXMLC) 4.5.1.21328, compiling in release mode (no debugging or verbose stack traces)
- Release version of Flash Player 11.1.102.55
- 2.4 Ghz Intel Core i5
- Mac OS X 10.7.2
And got these results:
Operation | Time |
---|---|
ByteArray serialize | 113 |
XML serialize | 554 |
ByteArray deserialize | 115 |
XML deserialize | 403 |
Clearly, ByteArray
outperforms XML
in both serialization (5x faster) and deserialization (4x faster). So, how does it do when comparing the storage size required? Well, the above performance test shows those figures too:
Format | Size |
---|---|
ByteArray | 1000063 |
XML | 7400017 |
Here too ByteArray
is the obvious winner over XML
with a 7x smaller data size.
In conclusion, ByteArray
‘s readObject
and writeObject
methods provide AS3 programmers with a great way to serialize their objects for sending over a network or storing for later retrieval. They support strong typing of these objects, collections like Vector
, class inheritance, and don’t require us to write any of the serialization or deserialization code.
Care to share your thoughts on serialization and deserialization? Post a comment!
#1 by Seba on November 21st, 2011 ·
Any idea of how to create an ByteArray on the server side that I am able to store? Say, for example, in PHP? (I mean without the whole Flash Remoting bloat?).
Kind Regards
#2 by jackson on November 21st, 2011 ·
You mean like AMFPHP?
#3 by Florian on November 21st, 2011 ·
Are you aware of the Transient metatag?
from the docs :
Use the [Transient] metadata tag to identifies a property that should be omitted from data that is sent to the server when an ActionScript object is mapped to a Java object using the [RemoteClass] metadata tag.
The [Transient] metadata tag has the following syntax:
#4 by jackson on November 21st, 2011 ·
Cool metatag. It could come in handy. Thanks for the tip.
#5 by Henke37 on November 21st, 2011 ·
That anything needs a major footnote.
Good luck serializing a bunch of complicated stuff. Like connections to other computers, filesystem access and a lot more.
#6 by jackson on November 21st, 2011 ·
True, but is it even possible to serialize a connection to another computer? I mean, it depends on the network and the other computer, not just the serialization and deserialization. Still, point taken. :)
#7 by Andreas Renberg on November 21st, 2011 ·
Sorry, just being a perfectionist here (and you can delete the comment when done).
In both pieces of code where this line appears, you missed the “Vector.” part:
bytes.writeObject(new [person, person2]);
Second, you are still using “setSecret()” rather than the setter function.
#8 by jackson on November 21st, 2011 ·
Actually, you can declare vectors like that. I even wrote an article about it awhile ago.
#9 by Miran on November 21st, 2011 ·
If only you could serialize a class with required constructor arguments…
#10 by ppold on November 21st, 2011 ·
I also tried doing that years ago but never found a solution.
#11 by Miran on November 21st, 2011 ·
I think all we can do atm is to make them all optional.
#12 by jackson on November 21st, 2011 ·
It’s an unfortunate limitation, but what can
readObject
do? I guess it could take a var args of parameters to pass, but with it supportingVector
s of objects, the API could get really nasty. I’d be interested to hear any ideas you have on the subject. :)#13 by Miran on November 21st, 2011 ·
Well, if we’re using readExternal to restore state of an object then we don’t really need a constructor, right? And because each class implicitly extends Object, it already has a constructor. That’s basically how java does things – as long as there’s at least one constructor, that has no arguments, in any given superclass, where at least one implements Serializable, everything should be fine. (http://download.oracle.com/javase/6/docs/api/java/io/Serializable.html)
But.. I don’t think that’s possible in as3.
Maybe as3-commons could help somehow? http://www.as3commons.org/
#14 by Aleksandr Makov on November 21st, 2011 ·
Thx, for the tip.
Do you have any practical use example? I personally don’t see any point of serialization/deserialization at all, except for sending/receiving real objects to outer environments through AMF. I haven’t done any of benchmarks, but heard the rumor that AMF transcoding is quite a resource killer, especially with Zend Framework. And by the way, setting up an AMF communication takes extra time.
Anyway, thanks for the effort and sharing.
#15 by jackson on November 21st, 2011 ·
I don’t have an example on hand, but imagine an AIR-based game level editor that wants to save to disk. You could easily serialize your
Level
data structure/class and write it out withFileStream
. The load functionality would simply be areadObject
, which would be great for cutting down load times and reducing the amount of code you have to write to parse a custom file format.#16 by Philip Bulley on November 22nd, 2011 ·
In fact, this article couldn’t have been better timed! This morning, I’m about to add a “Save project” feature to a small web-app I’m building which will allow the user to save/download their project to local disk. I was going to look into AMF serialisation, but this looks cleaner, and in all probability, faster. Thanks!
What’s all this talk of serialising massive/complex classes anyway (file system access?!?!). Personally, I can’t imagine needing to serialise anything other than value objects.
#17 by jackson on November 22nd, 2011 ·
Glad to be of help. :-D
#18 by Aleksandr Makov on November 28th, 2011 ·
Thanks a bunch! Perfect example!
#19 by Meinte on November 22nd, 2011 ·
To have more control over (de)serialization, an object can implement IExternalizable. This, for example, avoids having to make variables public which you’d otherwise keep private, just for the sake of serialization. Code example(taken from the docs)
class Example implement IExternalizable {
private var one:int;
private var two:int;
public function writeExternal(output:IDataOutput) {
output.writeInt(one);
output.writeInt(two);
}
public function readExternal(input:IDataInput) {
one = input.readInt();
two = input.readInt();
}
}
I believe you could even have an object with non-optional constructors by having the parent object, which implements IExternalizable, take care of instantiating in the readExternal function. But as said before, why bother. I usually only serialize VO’s anyway, and inject these in the Model after deserialization.
#20 by jackson on November 22nd, 2011 ·
Great tip! Yet another advantage to this form of serialization and deserialization. Thanks for posting the clear example, too.
#21 by ben w on November 23rd, 2011 ·
don’t forget to recommend using the “compress” and “uncompress” methods built into the bytearray class to squeeze even more space! its pretty fast two!
#22 by jpauclair on January 6th, 2012 ·
Can you do the same benchmark with the native JSON protocol?
http://help.adobe.com/en_US/FlashPlatform/beta/reference/actionscript/3/JSON.html
#23 by jackson on January 6th, 2012 ·
Sounds like a good idea for a followup article. :)
#24 by Glidias on February 19th, 2013 ·
Hmm, I didn’t know serialization with registerClassAlias still works automatically without the IExternalizable interface.
But I guess IExternalziable is useful if you wish to use compressed formats like reading shorts/bytes rather than full ints. I assume writing unsigned int vs signed ints is determined based on the variable type of uint vs int? Also, not necessarily all public read+write properties need to be serialized, depending on your application’s case. Thus, I seldom rely on auto-injections.
With registerClassAlias and readObject/writeObject, how much more additional disk space do you use in storing the data? I assume there’s a string-based class object key being used, thus, there might be a file-size overhead per instance being registered. The same might be the case when it comes to typed Vectors. What bytes in typed vectors are used when using readObject/writeObject approach? However, so far, I didn’t really bothered checking this, resorting to the convenience of readObject/writeObject even if it means a bit larger file-size.
Is there a performance difference in serializing objects that are auto-injected vs explicitly injected via IExternalizable interface.
#25 by Glidias on February 19th, 2013 ·
Anyway, auto-serialization with readObject/writeObject only works with primitive properties (String, int, uint, Number), I assume? (Even though String isn’t really a primitive data-type) and would still require registerClassAlias(“String”, String) if you wish you serialize strings as objects. Such serialization isn’t recursive of course, since only these “primitive” data-types are serialized. It also can’t resolve non-primitive references which are specific to an application, which would require something more sophisticated like separate lookup table(s) of objects and registering of indices to point to the right object reference.
#26 by jackson on February 20th, 2013 ·
If you want to learn more about the details of how AMF encodes the data, check out the specification. It’ll tell you all about how numbers, strings, and class names are stored. Strings, for example, are automatically supported as in the article. The same goes for other built-in types like
Array
andVector
. As for serializing custom objects and the performance difference withIExternalizable
, those are larger topics that I’ll have to do follow-up articles on. Thanks for the ideas!#27 by Arne Neugebauer on May 2nd, 2013 ·
Hey,
n1ce articel – Have you some idea how i can serialize an Texture Object from Stage3D? I have a Worker where i will transfer the ByteArray to VRAM – But when i give it the serialized byte array from the texture object i becam the Error Message: Error #1034: Type Coercion failed: cannot convert Object@2d7f0e39 to flash.display3D.textures.Texture. when i read it back into worker.
Thanks a Lot.
#28 by Arne Neugebauer on May 2nd, 2013 ·
My Mistake – Forgott registerClassAlias, but when i set this then i got the Error:
Texture$ class cannot be instantiated.
#29 by jackson on May 2nd, 2013 ·
The
Stage3D
API is a bit unusual compared to the rest of the Flash Player API. You don’t directly create theTexture
objects, but instead callContext3D.createTexture
and then one of theupload*
functions on it. This means that you can’t simply serialize aTexture
object the way you’d serialize other objects likeVector
orRectangle
.#30 by Ionut Pusca on May 30th, 2013 ·
Hello, awesome post! I have a question about serializing a huge array of objects, for example, you mentioned a vector of 2 objects, but how about a big array of 1000 objects? Can there be a loop that creates objects and pushes them into array and then serialize them? Can you please point me to the right direction? Thanks!
#31 by jackson on May 30th, 2013 ·
Thanks. You can serialize your
Array
orVector
of 1000 objects the same way you serialize one of any size:myByteArray.writeObject(myArray);
Or, if you’d rather write them out individually, you can do that too:for each (var obj:SomeType in myArray) myByteArray.writeObject(obj);
. Just make sure you read them in the same way you wrote them out. That is, if you write out aVector
, you should read in aVector
. If you write out 100 individual objects, you should read in 100 individual objects. TheArray
orVector
way is probably faster, easier, and more flexible but there will be a tiny bit of file size overhead to store the actualArray
orVector
object. Happy serializing!#32 by anoxamoon on December 25th, 2013 ·
this didn’t work… what am i doing wrong
import fl.data.DataProvider;
import flash.net.registerClassAlias;
import flash.utils.ByteArray;
var testDP:DataProvider = new DataProvider ();
testDP.addItem ({label:”basel”,data:{array:[[1,2,3],[4,5]]}});
trace (testDP.getItemAt(0).data.array[1][1])
trace (testDP);
registerClassAlias(“DataProvider”, DataProvider);
var bytes:ByteArray = new ByteArray();
bytes.writeObject(testDP);
bytes.position=0;
var newDP:DataProvider=bytes.readObject() as DataProvider;
trace (newDP,newDP.length);
trace (newDP.getItemAt(0).data.array[1][1])
#33 by jackson on December 25th, 2013 ·
Apparently
DataProvider
doesn’t serialize well. You could either serialize its data directly or extend it and implement theIExternalizable
interface as described here. You’d end up with a class that looks like this:Then you’d instantiate that class instead:
#34 by Joe on October 5th, 2014 ·
Thanks for the tip on private variables not being serialized. The same thing occurs for protected variables. Oddly, just having a ‘set’ will not result in serialization, you have to have the ‘get’ as well.
#35 by Renan on December 13th, 2014 ·
você me ajudou , muito obrigado , que Deus te abençoe