Serialization Error/Bug When Using a ByteArray and readObject in an IExternalizable Class?

I've encountered some odd behavior that I would have expected to work when calling readObject() to de-serialize an array of anonymous objects. If anyone knows what's going on here, please enlighten me. I've also filed a bug on Adobe's bug tracking system if anyone wants to follow the progress at Adobe's end.

I'm trying to read all of the bytes in the readExternal() function of a class implementing IExternalizable, so that I may use the position property to move back in stream in case I need to do so. The problem only seems to occur if I am trying to de-serialize an Array of anonymous objects. If I put plain old Strings in the Array it will work fine. I find this odd since I would expect

public function readExternal(input:IDataInput):void {  
  var arr:Array = input.readObject() as Array;
}

to have the exact same behavior as:

public function readExternal(input:IDataInput):void {  
  var ba:ByteArray = new ByteArray();
  var inputBytes:uint = input.bytesAvailable;
  input.readBytes(ba);
  var baBytes:uint = ba.bytesAvailable;
  var arr:Array = ba.readObject() as Array;
  trace("inputBytes == baBytes ?= " + (inputBytes == baBytes)); // traces "inputBytes == baBytes ?= true
}

AIR installer and code attached below...

The classes I'm trying to serialize are simple as I'm only trying to serialize an Array in this test application. (Excuse the poor code of assuming the items in the Array are Objects in the toString()

io/WorkingSerializer.as

package io {

    import flash.utils.IExternalizable;
    import flash.utils.IDataInput;
    import flash.utils.IDataOutput;

    public class WorkingSerializer implements IExternalizable {

        public var arr:Array;

        public function WorkingSerializer(a:Array=null) {
            arr = a ? a : [];
        }

        public function readExternal(input:IDataInput):void {
            arr = input.readObject() as Array;
        }

        public function writeExternal(output:IDataOutput):void {
            output.writeObject(arr);
        }

        public function toString():String {
            var s:String = "";
            for each(var obj:Object in arr) {
                s += "\n\t{ ";
                for(var p:String in obj)
                    s += p + ": " + obj[p] + ",";
                s += " }\n";
            }
            return "[WorkingSerializer]\n arr: [" + s + "]";
        }

    }
}

io/ProblemSerializer.as

package io {

    import flash.utils.ByteArray;
    import flash.utils.IExternalizable;
    import flash.utils.IDataInput;
    import flash.utils.IDataOutput;

    public class ProblemSerializer implements IExternalizable {

        public var arr:Array;

        public function ProblemSerializer(a:Array=null) {
            arr = a ? a : [];
        }

        public function readExternal(input:IDataInput):void {
            var ba:ByteArray = new ByteArray();
            input.readBytes(ba);
            arr = ba.readObject() as Array;
        }

        public function writeExternal(output:IDataOutput):void {
            output.writeObject(arr);
        }

        public function toString():String {
            var s:String = "";
            for each(var obj:Object in arr) {
                s += "\n\t{ ";
                for(var p:String in obj)
                    s += p + ": " + obj[p] + ",";
                s += " }\n";
            }
            return "[ProblemSerializer]\n arr: [" + s + "]";
        }

    }
}

And here is the main application file. I wrote it as an AIR application so install AIR first if you don't have it.

SerializationIssue.mxml

<?xml version="1.0" encoding="utf-8"?>  
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" creationComplete="init()"  
                        layout="vertical" horizontalAlign="center" width="500" height="500">

    <mx:Script>
        <![CDATA[
            import io.*;

            private const WORKING:String = "workingfile_v1";
            private const PROBLEM:String = "problemfile_v1";

            private function init():void {
                registerClassAlias("io.WorkingSerializer", io.WorkingSerializer);
                registerClassAlias("io.ProblemSerializer", io.ProblemSerializer);

                var wo:WorkingSerializer = new WorkingSerializer([{obj: "some"}, {obj: "items"}, {obj: "are"}, {obj: "here"}]);
                var s:FileStream = new FileStream();
                s.open(getFile(WORKING), FileMode.WRITE);
                s.writeObject(wo);
                s.close();

                var po:ProblemSerializer = new ProblemSerializer([{obj: "items"}, {obj: "in"}, {obj: "problem"}, {obj: "serializer"}]);
                s.open(getFile(PROBLEM), FileMode.WRITE);
                s.writeObject(po);
                s.close();
            }

            private function getFile(filename:String):File { return File.applicationStorageDirectory.resolvePath(filename); }

            private function readObj(readWorking:Boolean=false):void {
                var s:FileStream = new FileStream();
                s.open(getFile(readWorking ? WORKING : PROBLEM), FileMode.READ);

                if(readWorking) {
                    var wo:WorkingSerializer = s.readObject() as WorkingSerializer;
                    logger.text += wo + "\n\n";
                    trace(wo);
                }
                else {
                    var po:ProblemSerializer = s.readObject() as ProblemSerializer;
                    logger.text += po + "\n\n";
                    trace(po);
                }
                s.close();
            }
        ]]>
    </mx:Script>

    <mx:HBox>
        <mx:Button label="Read Working Serializer" click="readObj(true)" />
        <mx:Button label="Read Problem Serializer" click="readObj()" />
    </mx:HBox>

    <mx:Panel title="Logging" width="100%" height="100%">
        <mx:TextArea id="logger" width="100%" height="100%" editable="false" wordWrap="true"
                     updateComplete="logger.verticalScrollPosition=logger.maxVerticalScrollPosition" />
    </mx:Panel>

</mx:WindowedApplication>  

To test this out without having to compile it yourself, I've compiled it as an AIR installer: Serialization Issue AIR Application. And the source: Serialization Issue Source.

When I run the code below and click on the "Read Working Serializer" button I correctly see:

[WorkingSerializer]
 arr: [
    { obj: some, }

    { obj: items, }

    { obj: are, }

    { obj: here, }
]

get output to the console. But, if I click on the "Read Problem Serializer" button I get the following RangeError:

RangeError: Error #2006: The supplied index is out of bounds.  
at flash.utils::ByteArray/readObject()  
at io::ProblemSerializer/readExternal()[  
<path to environment>/SerializationIssue/src/io/ProblemSerializer.as:19]  
at flash.filesystem::FileStream/readObject()  
at SerializationIssue/readObj()[  
<path to environment>/SerializationIssue/src/SerializationIssue.mxml:40]  
at SerializationIssue/___SerializationIssue_Button2_click()[  
<path to environment>/SerializationIssue/src/SerializationIssue.mxml:51]  

In the meantime I'll find a workaround for this, but has anyone encountered anything similar to this or know why this isn't working?

comments powered by Disqus