There’s a few good use-cases for serialization; for me the first and foremost is the performance combined with ease of use.
In AIR we have SQLite access, or in a bad case we can write some format to a file and recover it later somehow via parsing. But why? That’s a lot of work… And it’s not the fastest approach either.
And what about if we’re doing a browser application that we want to save a state of something, but don’t want to host the storage and write a web service to handle data?
We don’t have stealth access to the filesystem directly, which makes it hard (errr, impossible) to silently store things on behalf of the user.
We can actually tackle both easily using serialization.
Quick note: I could quite possibly be wrong about some of these concepts, as they’re just things I’ve come across, and can’t find an exorbitant amount of documentation about these topics.
First thing’s first… Let’s take a quick look at some of the interfaces/classes we might run into:
- IExternalizable – When writing a byteArray object, it checks for this interface.
- IDataOutput – Output object for writing bytes to
- IDataInput – Input object, exact opposite of IDataOutput
- ByteArray – Our binary data source.
- Base64Encoder – Encodes bytes to a string representation
- Base64Decoder – Decodes a string to bytes
- FileReference – A mechanism to save to a user-appointed destination
The act of Serializing data in AS3
A quick serialization is rather simple, and we use the ByteArray to convert objects for us into AMF bytes. This is done in-memory, so we want to make sure our client systems can handle the amount/quantity of data we’re serializing. For most situations, this probably is not much of a concern.
Setting up a serialized Class
So in order for the Flash Player to know what to de/serialize to/from, we have to assign an Alias to our class.
This is done simply by using the RemoteObject metadata tag to define the mapping.
public class JunkVO
//…. Class Implementation ….//
Notice that I made my alias the fully qualified class name (that means I include the package name) my alias.
This is NOT required, but I personally suggest doing this. Keep reading to find out why…
If you want to map to a different name (such as something easier to remember, or to match the package space of a remote system) you can easily do so, and is acceptable.
The caveat to this is that in order to deserialize your objects, you MUST register the class alias using registerClassAlias():
public class JunkVO
//…. Class Implementation ….//
//** Before deserializing anywhere, register the class alias:
It’s typically best to register your aliases as soon as possible, such as on application startup.
Simple Serialization of an Object
Here’s a quick snippet of how to serialize your object:
var myJunkVo:JunkVO = new JunkVO();
var bytes:ByteArray = new ByteArray();
// set some properties on myJunkVo
bytes.writeObject(myJunkVo); //Write our object to the ByteArray
bytes.position = 0; //Reset position of ByteArray to the beginning
As a quick note, this is one half of the steps required to clone most objects at runtime. I’ll probably go over this in another post as well – in the meantime, check out mx.ObjectUtil::copy().
Done! Your object is now a ByteArray – you’ve serialized! Ok we can all go home now… Wait… I’m not actually done.
The whole object, and nothing but the whole object
a.k.a. It’s not working right
If you found yourself going through this post, or any other post you’ve found, but things just aren’t working right – this section is most likely the concept you missed.
There’s 2 considerations to serializing your data:
- If your object hierarchy is made of completely public properties, then there really is nothing extra special you need to do in order to serialize…
Seriously, I’ve never had to do anything fancy for fully public classes.
- If your object has complex objects as children, which utilizes encapsulation and needs to save its complete state; well then we have some extra footwork to do.
This is necessary for objects that we can’t easily derive states from our public variables, and have maybe collections of custom objects. Essentially, the nature of the data is valuable for an instance to function properly.
It’s an all or nothing thing too – you either rely on all public vars and everything’s dandy, or if you want to serialize private/protected vars you’ll need to ALSO write the public ones!
So #1 is what we saw above. Assuming all members of JunkVO are public, we can safely serialize as demo’d.
As it turns out, #2 isn’t that difficult to pull off, but can be rather tedious depending on your model.
Implementing External Serialization
If you implement the IExternalizable interface, when a ByteArray::writeObject()/readObject() is called, internally it checks for these interfaces and calls the appropriate method.
The trick to implementing these methods is that you do it in the exact same order. For every write, you read, no exceptions! You’re essentially manipulating the ByteArray your object is stored in.
public function writeExternal(output:IDataOutput):void
public function readExternal(input:IDataInput):void
this._createdDate = input.readObject();
this.somePropInt = input.readInt();
this.somePropString = input.readObject();
See, pretty simple!
- Sub-objects could also need to implement the interface for recursive de/serialization to occur properly. If they appear as null or default values when inspecting, count on this being the issue!
- Every property MUST be written and read in the same exact order.
Things to do with your serialized ByteArray
Well, you can take your ByteArray, and shove it right up your ……. storage mechanism 😀
The easiest way to handle this is writing the ByteArray directly into a file:
protected var _fileReference:FileReference;
protected function save():void
var ba:ByteArray = new ByteArray();
ba.compress(); //compress to save space
this._fileReference = new FileReference();
//Prompt the user to save out to a file. Extension is made up here btw
But wait, there’s more!
While this works, and is quick and useful – I prefer to encode the binary data into a String.
This may not be that big of a deal for most of you, but for me personally I like to be able to copy the output and paste it into a sandbox app to see what’s going on.
I might make this app as simple and quick as possible, so I only have a text field to put the data, and binary data gets all wacky when copy/pasting.
I might also want to store the data into a database column to void a bunch of key/value tables that muddy things up. Binary data can be a pain working with in a database, and generally adds to confusion.
So for that, I’ve implemented a nice utility class I call SerializeUtils. You can see it in full effect in the example, or just take a look at the interface – it’s super simple!
Using the same file-save technique above, you can now save this text out to a file – just replace the byteArray reference with your string!
Things don’t always go as planned. Here’s some things that I’ve found along the way:
- When deserializing, and things are turning into Objects, or ObjectProxy, then we need to check our Alias’.
- If deserializing a built-in Flash object (e.g.: Date), be sure to register the class alias with the fully qualified class name.
- Not all objects will serialize correctly. Unfortunately the only work around is to store the variables in a custom class of your own, and serialize that data.
- An error about converting an Object when writing a ByteArray: Ensure your metadata alias, and/or your registerClassAlias() is correct/exists.
- For writing to a file, make sure the client is using Flash Player 10+, this is a new-ish feature.
Example, Source, and Additional Info
To prove this works, check out the example. Once you serialize the object, copy the Base64Encoded string, and close/reopen the SWF. If you paste it back in and deserialize, you should see that the values are the same as you entered them originally, including the date the object was created!
Some Additional Information: