Serialization: IExernalizable and Evolving Classes September 12, 2007
What is evolving a class? It's the inevitable:
- addition of a field
- change the superclass
- remove a field
- change the name of a field
- change a field to static (from non-static)
- change a field to transient (from non-transient)
- change the type of a field type (e.g. int to float)
As soon as you do any of the above whatever you've previously saved something to disk, be it local or remote will break upon loading back in.
This isn't common in most web based flash development which lives in isolation on a webpage and doesn't save anything, but introduce any sort of saving. With Desktop Flash apps (AIR, Zinc), and RIA's, offline online applications, it IS becoming something that is important.
There are 3 different strategies to serializing objects, only one is particularly useful for evolving classes.
- Direct:
writeObject(yourClassObject); - Customize with IExternalizeable: iterate over each attribute of your object and write each of those to the stream.
- Customize with IExternalizeable, iterate over each attribute of your object write it with a key to a Dictionary / HashTable.
Approach 1 is great for simple uses and first passes. However it doesn't scale well or at all. Approach 2 Is better but as the stream is sequential, this can make dealing with multiple versions difficult or impossible as you have to know the exact order in which to parse. Approach 3 is the best, it uses a Dictionary as an intermediate map, it's only a bit more code to write, and is a *whole* lot easier to parse. This is also the approach that Remoting uses when talking to the server with the ASObject, and in someways writing to disk is identical to writing to the filesystem.
Here is the list of imports:
-
-
import flash.utils.Dictionary;
-
import reflection.introspectable;
-
import flash.utils.IExternalizable;
-
import flash.utils.IDataInput;
-
import flash.utils.IDataOutput;
-
import flash.net.registerClassAlias;
Here is the version number, we'd need to increase everytime a significant data change has happened, and some of the static register class necessary to get the class to serialize and deserialize correctly:
-
-
public static const serialVersionUID:Number = 01;
-
private static const REG_DICT:* = registerClassAlias("flash.utils.Dictionary",Dictionary);
-
private static const REG:* = registerClassAlias("reflection.IntrospectableObj",IntrospectableObj);
-
[RemoteClass(alias="reflection.IntrospectableObj")]
-
Here are the modified IExternalizable functions. Note that the read from the stream has to deal with all permutations for a given version to version, as this number of changes gets large, the normalization can be quite large, thankfully good architecture up front can prevent much of this.
-
-
///////////////////////////////////////////////
-
public function readExternal(input:IDataInput):void
-
{
-
var d:Dictionary = input.readObject();
-
var serialVersionUID:Number = d["serialVersionUID"] as Number ;
-
trace("readExternal " + serialVersionUID);
-
/////////////// COMMON TO ALL VERSIONS ///////////////////////////
-
if(serialVersionUID == 1 || serialVersionUID == 2 || serialVersionUID == 3){
-
introspectable::introVarStr = d["introVarStr"]as String;
-
defVarStr = d["defVarStr"]as String;
-
-
pubByteArray =d["pubByteArray"] as ByteArray;
-
pubBitmapData= (d["pubBitmapData"] as BitmapDataWrapper).getBitMapData();
-
}
-
/////////////// COMMON TO TWO VERSIONS //////////////////////
-
if(serialVersionUID == 1 || serialVersionUID == 2){
-
}
-
-
if(serialVersionUID == 2 || serialVersionUID == 3){
-
-
}
-
if(serialVersionUID == 1 || serialVersionUID == 3){
-
-
}
-
///////////// COMMON TO ONE VERSION /////////////////////////
-
if(serialVersionUID == 1 ){
-
-
}else if(serialVersionUID == 2 ){
-
}else if(serialVersionUID == 3 ){
-
}
-
-
}
-
-
public function writeExternal(output:IDataOutput):void
-
{
-
trace("writeExternal....");
-
var d:Dictionary = new Dictionary();
-
d["serialVersionUID"] = serialVersionUID;
-
-
// name spaced var
-
d["introVarStr"] = introspectable::introVarStr;
-
d["defVarStr"] = defVarStr;
-
-
// this has already packed data
-
d["pubByteArray"] = pubByteArray;
-
-
// since BitmapData can't be retrieved, we've created a wrapper that can be.
-
d["pubBitmapData"] = new BitmapDataWrapper(pubBitmapData);
-
output.writeObject(d);
-
}

Add a Comment: