Custom Serialization using IExternalizable
I started a blog to document solutions to questions that I found myself frequently answering on the Flex forums. So the first topic up for discussion is using custom serialization with value objects in RemoteObject. This solution takes advantage of the ActionScript 3 API flash.utils.IExternalizable which is compatible with Java's java.io.IExternalizable API. By implementing these interfaces on the client and server your value objects can take control of their serialization and customize what data is sent over the wire.
A common use case for using externalizable classes is to include read-only properties in serialization. While there are other approaches to achieve this for server code, there aren't many approaches available for client code. The server uses the Java Beans API when serializing classes so one could change their value objects to implement java.beans.BeanInfo to customize introspection, however there isn't an equivalent for this in ActionScript. This may be fine if data is not editable on the client - but many applications involve data manipulation so read-only information needs to be transmitted to maintain the idenity and/or state of an instance. A simpler, brute-force approach might be to track when a setter has been called and throw an error if it is called again - but the fact that your value object had read-only properties wouldn't be obvious from your API.
So, for an elegant solution that works for both the client and server we are left with the option of making our classes externalizable for two-way custom serialization. Thankfully this is relatively straight-forward. The client ActionScript class simply implements flash.utils.IExternalizable. This API requires two methods readExternal() and writeExternal() which take flash.utils.IDataInput and flash.utils.IDataOutput streams respectively. The implementations of these methods mirror the server Java class, which implements java.io.Externalizable - also with two methods readExternal() and writeExternal() taking java.io.ObjectInput and java.io.ObjectOutput streams respectively.
While the IDataInput and IDataOutput classes let you design your own protocol and write fundamental data types such as byte, int, and UTF-8 encoded Strings, most implementations will take advantage of the readObject() and writeObject() methods respectively as these use AMF 3 to efficiently deserialize and serialize ActionScript objects. (Remember that AMF 3 allows: (a) objects to be sent by reference to avoid redundant instances from being serialized, to retain object relationships and to handle cyclical references, (b) object traits to be sent so that the description of a type is only sent once rather than them repeated for each instance, and (c) reoccuring strings can be sent by reference to again avoid redundant information from being sent). One may even decide to omit property names altogether in their externalizable classes' custom serialization code and rely on a fixed order to send just the property values.
To see a code example, check out externalizable.zip. While this example focuses on serializing read-only properties there may be many other usages for custom serialization such as omitting properties, avoiding redundant serialization of information or including properties from custom namespaces.
Notice how the server Java writeExternal() method:
public void writeExternal(ObjectOutput out) throws IOException
{
out.writeObject(id);
out.writeObject(name);
out.writeObject(description);
out.writeInt(price);
}
...mirrors the client readExternal method in ActionScript:
public function readExternal(input:IDataInput):void
{
_id = input.readObject() as String;
name = input.readObject() as String;
description = input.readObject() as String;
price = input.readInt();
}
A similar relationship exists for the reverse situation for sending instances back from the client to the server.
Comments
Pete,
I am getting errors on flex side during deserialization .
ArgumentError: Error #2004: One of the parameters is invalid.
at flash.utils::ObjectInput/readObject()
I guess that flex does not recognize/link flash.utils.IExternalizable interface and is trying to deserialize all the classes in a normal way.
Is this a bug or is there a work around
Thanks
Karthik
Posted by: karthik | April 28, 2007 3:24 AM
It's not that Flash or Flex does not recognize the interface, it's that it can't find a class definition that matches the reported implementation. You need to create a reference to the Class somewhere in your Flex application (i.e. type a private static variable with that class, or use that Class in a casting statement on the result or a method signature - whatever - just some way to make the compiler note the dependency on this so it will link it into the SWF).
Posted by: Pete | April 28, 2007 11:05 AM
Great post Pete, I've extended what you've done with some alternate approaches to dealing with data you don't want serialized and evolving classes.
http://troyworks.com/blog/?cat=30
Posted by: Troy Gardner | September 12, 2007 4:19 AM
I discovered that by default FDS don't contain RemoteClass metdata in section of flex-config.xml. That eat about of my 2 hours. Should I post bug somewhere?
Posted by: Den | October 22, 2007 12:06 PM
I create the following function
---------------------------------
package com.cerebrum.utils {
import mx.rpc.events.ResultEvent
import mx.controls.Alert;
public class Result {
[Embed("/assets/images/dialog-information.png")]
public var IconDialogInformation:Class;
public function Result(event:ResultEvent,MyObject:Object):void {
Alert.show("teste", "teste", 4, null, null, IconDialogInformation);
}
}
}
---------------------------------
I as follows
import Result;
Result(event, meuobjeto);
---------------------------------
Is presented in the following error flex builder
1137: Incorrect number of arguments. Expected no more than 1.
---------------------------------
How should I inform the function that actually has 2 parameters?
Posted by: Marcio | March 14, 2008 2:37 PM
You cannot register an event listener that has more than one argument. If you want to retain more information, get the mx.rpc.AsyncToken instance which is returned immediately in ActionScript from a call to any RemoteObject method, decorate this token (it's dynamic, so it should be just like a "hashmap" like AS Object is) with extra info, and then get the token back from the ResultEvent instance in your handler.
Posted by: Pete Farland | March 14, 2008 2:59 PM
Hey Pete,
I have an object that has a property "children" (an ArrayCollection) that contains more of this same object, and that child object could have more children as well. The object implements IExternalizable. If my test object has no children, it will externalize just fine. Well that's a problem because I want to externalize the children as well. My writeExternal is below:
With children in the object's AC it crashes on line 452 of ArrayList. It's the writeObject(_source) call. Is it because the source contains my custom object? How do I make it work?
Posted by: Troy A. Binford | March 22, 2008 12:55 PM
Ignore previous comment please. It was the same as the first issue. As soon as I registered the class the serialization worked. Thanks!
Posted by: Troy A. Binford | March 22, 2008 1:11 PM
i don't think that i'm double posting what karthik posted, but...
so if you forget to add the [RemoteClass(alias="com.blah.Foo")] metadata, FR/LCDS will have problem sending an object to the CLIENT and typing it correctly. It also wont have any trouble sending an object to the SERVER, so long as you didn't implement IExternalizable. If you did, and you leave of the metadata, it just doesn't call the writeExternal() method and tries to serialize the entire object graph. i no clue what's wrong with my object where if it tries to serialize the entire object it get the same error as karthik, but once i put in the missing metadata, it called my writeExternal() method and all was well.
Posted by: Jeffrey A Lage | May 1, 2008 6:57 PM