├── actionjson.swc ├── .gitignore ├── README.markdown └── as3 └── com └── brokenfunction └── json ├── build.xml ├── encodeJson.as ├── JsonEncoderAsync.as ├── decodeJson.as ├── JsonDecoderAsync.as └── TestJson.as /actionjson.swc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mherkender/actionjson/HEAD/actionjson.swc -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | as3/com/adobe 2 | as3/com/brokenfunction/json/bin 3 | tools/ason 4 | tools/apparat 5 | tools/blooddy_crypto 6 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | *With the release of Flash 11 (Oct 2011), Adobe has added native JSON support to Flash, which is significantly faster than anything that can be written in AS3. For projects that want to take advantage of this, while still staying compatible with Flash 9 and 10, there is a new argument in decodeJson and encodeJson that will use the native parser if it is available, giving developers the best of both words.* 2 | 3 | actionjson includes four projects 4 | 5 | decodeJson - A very fast JSON decoder. Around 5-8x faster than as3corelib's JSON decoder 6 | 7 | encodeJson - A very fast json encoder. Around 3x faster than as3corelib's JSON encoder 8 | 9 | JsonDecoderAsync - An asynchronous JSON parser. Can parse JSON in chunks, making it great for parsing large objects over time. Still around 2x faster than as3corelib's JSON decoder 10 | 11 | JsonEncoderAsync - An asynchronous JSON encoder. Encodes JSON in chunks, for large objects that need to be encoded over time. Sadly, about the same speed as as3corelib's JSON encoder 12 | 13 | Download the library as a swc here: 14 | http://github.com/mherkender/actionjson/raw/master/actionjson.swc 15 | -------------------------------------------------------------------------------- /as3/com/brokenfunction/json/build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 33 | 34 | 35 | 11.1.0 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /as3/com/brokenfunction/json/encodeJson.as: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Maximilian Herkender 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.brokenfunction.json { 17 | /** 18 | * Convert a String or ByteArray from an object to an a JSON-encoded string. 19 | * 20 | * This will either return the result as a string, or write it directly to 21 | * an IDataOutput output stream if the "writeTo" argument is supplied. 22 | * 23 | * To take advantage of the native Flash JSON encoder, set allowNative to true. Since 24 | * the behavior of the native encoder is slightly different when it comes to encoding 25 | * specialized objects, this should only be used on input with simple objects, arrays, 26 | * numbers, booleans and strings. Other features, like the toJson method, are not 27 | * used. See the AS3 JSON reference (link below) for more information. 28 | * 29 | * Currently strictNumberSupport must be true and writeTo must be null to use 30 | * native parsing. This may change, so only set allowNative to true if the differences 31 | * are not important. 32 | * 33 | * Warning: Vectors are not supported, they will be encoded as empty objects. 34 | * 35 | * @parameter input An object to convert to JSON. 36 | * @parameter writeTo An optional IDataOutput output stream to write data to. 37 | * @parameter allowNativeJson Allow using native json encoding in certain situations. This 38 | * changes the behavior of the encoder based on Flash version, so use it carefully. 39 | * @return A valid JSON-encoded string if writeTo is not specified, otherwise 40 | * null is returned. 41 | * @see http://json.org/ 42 | * @see http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/JSON.html 43 | */ 44 | public const encodeJson:Function = initDecodeJson(); 45 | } 46 | 47 | import flash.system.ApplicationDomain; 48 | import flash.utils.ByteArray; 49 | import flash.utils.IDataOutput; 50 | 51 | function initDecodeJson():Function { 52 | var result:IDataOutput; 53 | var i:int, j:int, strLen:int, str:String, char:int; 54 | var tempBytes:ByteArray = new ByteArray(); 55 | var blockNonFiniteNumbers:Boolean; 56 | 57 | var nativeJson:Object; 58 | try { 59 | nativeJson = ApplicationDomain.currentDomain.getDefinition("JSON"); 60 | } catch (e:ReferenceError) { 61 | // ignore 62 | } 63 | 64 | 65 | const charConvert:Array = new Array(0x100); 66 | for (j = 0; j < 0xa; j++) { 67 | charConvert[j] = (j + 0x30) | 0x30303000;// 000[0-9] 68 | } 69 | for (; j < 0x10; j++) { 70 | charConvert[j] = (j + 0x37) | 0x30303000;// 000[A-F] 71 | } 72 | for (;j < 0x1a; j++) { 73 | charConvert[j] = (j + 0x20) | 0x30303100;// 00[1][0-9] 74 | } 75 | for (;j < 0x20; j++) { 76 | charConvert[j] = (j + 0x27) | 0x30303100;// 00[1][A-F] 77 | } 78 | for (;j < 0x100; j++) { 79 | charConvert[j] = j; 80 | } 81 | charConvert[0xa] = 0x5c6e; // \n 82 | charConvert[0xd] = 0x5c72; // \r 83 | charConvert[0x9] = 0x5c74; // \t 84 | charConvert[0x8] = 0x5c62; // \b 85 | charConvert[0xc] = 0x5c66; // \f 86 | charConvert[0x8] = 0x5c62; // \b 87 | charConvert[0x22] = 0x5c22; // \" 88 | charConvert[0x5c] = 0x5c5c; // \\ 89 | // not necessary for valid json 90 | //charConvert[0x2f] = 0x5c2f; // \/ 91 | charConvert[0x7f] = 0x30303746; // 007F 92 | 93 | const parseArray:Function = function (data:Array):void { 94 | result.writeByte(0x5b);// [ 95 | var k:int = 0; 96 | var len:int = data.length - 1; 97 | if (len >= 0) { 98 | while (k < len) { 99 | parse[typeof data[k]](data[k]); 100 | result.writeByte(0x2c);// , 101 | k++; 102 | } 103 | parse[typeof data[k]](data[k]); 104 | } 105 | result.writeByte(0x5d);// ] 106 | } 107 | const parseString:Function = function (data:String):void { 108 | result.writeByte(0x22);// " 109 | tempBytes.position = 0; 110 | tempBytes.length = 0; 111 | tempBytes.writeUTFBytes(data); 112 | i = 0; 113 | j = 0; 114 | strLen = tempBytes.length; 115 | while (j < strLen) { 116 | char = charConvert[tempBytes[j++]]; 117 | if (char > 0x100) { 118 | if (j - 1 > i) { 119 | // flush buffered string 120 | result.writeBytes(tempBytes, i, (j - 1) - i); 121 | } 122 | if (char > 0x10000) {// \uxxxx (control character) 123 | result.writeShort(0x5c75);// \u 124 | result.writeUnsignedInt(char); 125 | } else { 126 | result.writeShort(char); 127 | } 128 | i = j; 129 | } 130 | } 131 | // flush the rest of the string 132 | if (strLen > i) { 133 | result.writeBytes(tempBytes, i, strLen - i); 134 | } 135 | result.writeByte(0x22);// " 136 | } 137 | 138 | const parse:Object = { 139 | "object": function (data:Object):void { 140 | if (data) { 141 | if (data is Array) { 142 | parseArray(data); 143 | } else { 144 | result.writeByte(0x7b);// { 145 | var first:Boolean = true; 146 | for (str in data) { 147 | if (first) { 148 | first = false; 149 | } else { 150 | result.writeByte(0x2c);// , 151 | } 152 | parseString(str); 153 | result.writeByte(0x3a);// : 154 | parse[typeof data[str]](data[str]); 155 | } 156 | result.writeByte(0x7d);// } 157 | } 158 | } else { 159 | result.writeUnsignedInt(0x6e756c6c);// null 160 | } 161 | }, 162 | "string": parseString, 163 | "number": function (data:Number):void { 164 | if (blockNonFiniteNumbers && !isFinite(data)) { 165 | throw new Error("Number " + data + " is not encodable"); 166 | } 167 | result.writeUTFBytes(String(data)); 168 | }, 169 | "boolean": function (data:Boolean):void { 170 | if (data) { 171 | result.writeUnsignedInt(0x74727565);// true 172 | } else { 173 | result.writeByte(0x66);// f 174 | result.writeUnsignedInt(0x616c7365);// alse 175 | } 176 | }, 177 | "xml": function (data:Object):void { 178 | if ((!data.toXMLString is Function) || (data = data.toXMLString() as String) == null) { 179 | throw new Error("unserializable XML object encountered"); 180 | } 181 | parseString(data); 182 | }, 183 | "undefined": function (data:Boolean):void { 184 | result.writeUnsignedInt(0x6e756c6c);// null 185 | } 186 | }; 187 | 188 | return function (input:Object, writeTo:IDataOutput = null, strictNumberSupport:Boolean = false, allowNativeJson:Boolean = false):String { 189 | // prepare the input 190 | var byteOutput:ByteArray; 191 | blockNonFiniteNumbers = strictNumberSupport; 192 | try { 193 | if (writeTo) { 194 | result = writeTo; 195 | result.endian = "bigEndian"; 196 | parse[typeof input](input); 197 | byteOutput.position = 0; 198 | return byteOutput.readUTFBytes(byteOutput.length); 199 | } else if (allowNativeJson && strictNumberSupport && nativeJson) { 200 | return nativeJson.stringify(input); 201 | } else { 202 | switch (typeof input) { 203 | case "xml": 204 | if ((!input.toXMLString is Function) || (input = input.toXMLString() as String) == null) { 205 | throw new Error("unserializable XML object encountered"); 206 | } 207 | case "object": 208 | case "string": 209 | result = byteOutput = new ByteArray(); 210 | result.endian = "bigEndian"; 211 | parse[typeof input](input); 212 | byteOutput.position = 0; 213 | return byteOutput.readUTFBytes(byteOutput.length); 214 | case "number": 215 | if (blockNonFiniteNumbers && !isFinite(input as Number)) { 216 | throw new Error("Number " + input + " is not encodable"); 217 | } 218 | return String(input); 219 | case "boolean": 220 | return input ? "true" : "false"; 221 | case "undefined": 222 | return "null"; 223 | default: 224 | throw new Error("Unexpected type \"" + (typeof input) + "\" encountered"); 225 | } 226 | } 227 | } catch (e:TypeError) { 228 | throw new Error("Unexpected type encountered"); 229 | } 230 | return null; 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /as3/com/brokenfunction/json/JsonEncoderAsync.as: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Maximilian Herkender 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.brokenfunction.json { 17 | import flash.utils.ByteArray; 18 | import flash.utils.IDataOutput; 19 | 20 | /** 21 | * An asynchronous JSON encoder. 22 | * @see http://json.org/ 23 | */ 24 | public class JsonEncoderAsync { 25 | private static var _charConvert:Array; 26 | 27 | private var _output:IDataOutput; 28 | private var _byteOutput:ByteArray; 29 | private var _blockNonFiniteNumbers:Boolean; 30 | private var _tempBytes:ByteArray = new ByteArray(); 31 | 32 | /** 33 | * This is the function to execute to continue processing. If it is null 34 | * there is nothing left to process. Functions modify stackTop to set what 35 | * must be processed next. 36 | * @private 37 | */ 38 | private var stackTop:Function; 39 | 40 | /** 41 | * @parameter input An object to convert to JSON. 42 | * @parameter writeTo An optional IDataOutput output stream to write data to. 43 | * @see http://json.org/ 44 | */ 45 | public function JsonEncoderAsync(input:*, writeTo:IDataOutput = null, strictNumberParsing:Boolean = false):void { 46 | _blockNonFiniteNumbers = strictNumberParsing; 47 | 48 | // prepare the input 49 | if (writeTo) { 50 | _output = writeTo; 51 | } else { 52 | _output = _byteOutput = new ByteArray(); 53 | } 54 | _output.endian = "bigEndian"; 55 | 56 | // conversions for escaped characters 57 | if (!_charConvert) { 58 | _charConvert = new Array(0x100); 59 | for (var j:int = 0; j < 0xa; j++) { 60 | _charConvert[j] = (j + 0x30) | 0x30303000;// 000[0-9] 61 | } 62 | for (; j < 0x10; j++) { 63 | _charConvert[j] = (j + 0x37) | 0x30303000;// 000[A-F] 64 | } 65 | for (;j < 0x1a; j++) { 66 | _charConvert[j] = (j + 0x20) | 0x30303100;// 00[1][0-9] 67 | } 68 | for (;j < 0x20; j++) { 69 | _charConvert[j] = (j + 0x27) | 0x30303100;// 00[1][A-F] 70 | } 71 | for (;j < 0x100; j++) { 72 | _charConvert[j] = j; 73 | } 74 | _charConvert[0xa] = 0x5c6e; // \n 75 | _charConvert[0xd] = 0x5c72; // \r 76 | _charConvert[0x9] = 0x5c74; // \t 77 | _charConvert[0x8] = 0x5c62; // \b 78 | _charConvert[0xc] = 0x5c66; // \f 79 | _charConvert[0x8] = 0x5c62; // \b 80 | _charConvert[0x22] = 0x5c22; // \" 81 | _charConvert[0x5c] = 0x5c5c; // \\ 82 | // not necessary for valid json 83 | //_charConvert[0x2f] = 0x5c2f; // \/ 84 | _charConvert[0x7f] = 0x30303746; // 007F 85 | } 86 | 87 | // the initial stack function 88 | stackTop = function ():void { 89 | stackTop = null; 90 | parseValue(input); 91 | } 92 | } 93 | 94 | /** 95 | * A valid JSON-encoded string if writeTo is not specified, otherwise 96 | * this will throw an error. 97 | */ 98 | public function get result():String { 99 | if (!_byteOutput) { 100 | throw new Error("No result available when writeTo is used."); 101 | } 102 | if (stackTop != null) { 103 | process(); 104 | } 105 | _byteOutput.position = 0; 106 | return _byteOutput.readUTFBytes(_byteOutput.length); 107 | } 108 | 109 | /** 110 | * Continue processing data. Warning, there are currently no limits placed 111 | * on string processing. 112 | * 113 | * Warning: Vectors are not supported, they will be encoded as empty objects. 114 | * 115 | * @property limit Stop processing after this many chunks of data. Because 116 | * of limitations in Flash, it isn't very strict, and depends on the data 117 | * being processed. Find a number that works. If zero, process will continue 118 | * until there is nothing left to be processed 119 | * @return True if processing is finished, otherwise process will return 120 | * false. 121 | */ 122 | public function process(limit:uint = 0):Boolean { 123 | if (limit) { 124 | while (stackTop != null) { 125 | stackTop(); 126 | if (limit-- > 0) { 127 | return false; 128 | } 129 | } 130 | } else { 131 | while (stackTop != null) { 132 | stackTop(); 133 | } 134 | } 135 | return true; 136 | } 137 | 138 | private function parseValue(input:*):void { 139 | switch (typeof input) { 140 | case "object": 141 | if (input) { 142 | if (input is Array) { 143 | parseArray(input); 144 | } else { 145 | parseObject(input); 146 | } 147 | } else { 148 | _output.writeUnsignedInt(0x6e756c6c);// null 149 | } 150 | return; 151 | case "string": 152 | parseString(input); 153 | return; 154 | case "number": 155 | if (_blockNonFiniteNumbers && !isFinite(input as Number)) { 156 | throw new Error("Number " + input + " is not encodable"); 157 | } 158 | _output.writeUTFBytes(String(input)); 159 | return; 160 | case "xml": 161 | if ((!input.toXMLString is Function) || (input = input.toXMLString() as String) == null) { 162 | throw new Error("unserializable XML object encountered"); 163 | } 164 | parseString(input); 165 | return; 166 | case "boolean": 167 | if (input) { 168 | _output.writeUnsignedInt(0x74727565);// true 169 | } else { 170 | _output.writeByte(0x66);// f 171 | _output.writeUnsignedInt(0x616c7365);// alse 172 | } 173 | return; 174 | case "undefined": 175 | _output.writeUnsignedInt(0x6e756c6c);// null 176 | return; 177 | }; 178 | } 179 | 180 | private function parseArray(input:Array):void { 181 | _output.writeByte(0x5b);// [ 182 | 183 | var stackNext:Function = stackTop; 184 | var length:int = input.length; 185 | if (length >= 2) { 186 | var pos:int = 1; 187 | stackTop = function ():void {// parse values 1-n 188 | _output.writeByte(0x2c);// , 189 | if (pos >= length - 1) { 190 | stackTop = function ():void {// end processing 191 | stackTop = stackNext; 192 | _output.writeByte(0x5d);// ] 193 | } 194 | } 195 | parseValue(input[pos++]); 196 | } 197 | parseValue(input[0]); 198 | } else if (length >= 1) { 199 | stackTop = function ():void {// end processing 200 | stackTop = stackNext; 201 | _output.writeByte(0x5d);// ] 202 | } 203 | parseValue(input[0]); 204 | } else { 205 | _output.writeByte(0x5d);// ] 206 | } 207 | } 208 | 209 | private function parseObject(input:Object):void { 210 | _output.writeByte(0x7b);// { 211 | 212 | var stackNext:Function = stackTop; 213 | var keys:Array = []; 214 | for (var i:String in input) { 215 | keys[keys.length] = i; 216 | } 217 | var length:int = keys.length; 218 | if (length >= 2) { 219 | var pos:int = 1; 220 | var value:Function = function ():void {// process an object value 221 | if (pos >= length - 1) { 222 | stackTop = function ():void {// end processing 223 | stackTop = stackNext; 224 | _output.writeByte(0x7d);// } 225 | } 226 | } else { 227 | stackTop = key; 228 | } 229 | _output.writeByte(0x3a);// : 230 | parseValue(input[keys[pos++]]); 231 | } 232 | var key:Function = function ():void {// process an object key after a value 233 | stackTop = value 234 | _output.writeByte(0x2c);// , 235 | parseString(keys[pos]); 236 | } 237 | stackTop = function ():void {// process first object key 238 | stackTop = key; 239 | _output.writeByte(0x3a);// : 240 | parseValue(input[keys[0]]); 241 | } 242 | parseString(keys[0]); 243 | } else if (length >= 1) { 244 | stackTop = function ():void {// process only object key 245 | stackTop = function ():void {// process only object value 246 | _output.writeByte(0x7d);// } 247 | stackTop = stackNext; 248 | } 249 | _output.writeByte(0x3a);// : 250 | parseValue(input[keys[pos]]); 251 | } 252 | parseString(keys[0]); 253 | } else { 254 | _output.writeByte(0x7d);// } 255 | } 256 | } 257 | 258 | private function parseString(input:String):void { 259 | _output.writeByte(0x22);// " 260 | _tempBytes.position = 0; 261 | _tempBytes.length = 0; 262 | _tempBytes.writeUTFBytes(input); 263 | var i:int = 0, j:int = 0, char:int; 264 | var strLen:int = _tempBytes.length; 265 | strloop:while (j < strLen) { 266 | char = _charConvert[_tempBytes[j++]]; 267 | if (char > 0x100) { 268 | if (j - 1 > i) { 269 | // flush buffered string 270 | _output.writeBytes(_tempBytes, i, (j - 1) - i); 271 | } 272 | if (char > 0x10000) {// \uxxxx (control character) 273 | _output.writeShort(0x5c75);// \u 274 | _output.writeUnsignedInt(char); 275 | } else { 276 | _output.writeShort(char); 277 | } 278 | i = j; 279 | } 280 | } 281 | // flush the rest of the string 282 | if (strLen > i) { 283 | _output.writeBytes(_tempBytes, i, strLen - i); 284 | } 285 | _output.writeByte(0x22);// " 286 | } 287 | } 288 | } 289 | -------------------------------------------------------------------------------- /as3/com/brokenfunction/json/decodeJson.as: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Maximilian Herkender 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.brokenfunction.json { 17 | /** 18 | * Convert a String or ByteArray from a JSON-encoded object to an actual 19 | * object. It's better to supply a ByteArray, if possible, since a String will 20 | * be converted to a ByteArray anyway. Keep in mind the endian or position of 21 | * a ByteArray will not be preserved. Data in the ByteArray will not be 22 | * modified. 23 | * 24 | * This should be compatible with all valid JSON, and may parse some kinds 25 | * invalid JSON, but generally this is very strict. 26 | * 27 | * If the native JSON decoder is available (Flash 11+) it can be used instead if the allowNativeJson parser is available and the input is a string. 28 | * 29 | * @parameter input A JSON-encoded ByteArray or String. 30 | * @parameter allowNativeJson Use the Native JSON. 31 | * @return The object created by the JSON decoding. 32 | * @see http://json.org/ 33 | */ 34 | public const decodeJson:Function = initDecodeJson(); 35 | } 36 | 37 | import flash.system.ApplicationDomain; 38 | import flash.utils.ByteArray; 39 | 40 | function initDecodeJson():Function { 41 | var position:uint; 42 | var byteInput:ByteArray; 43 | var char:uint; 44 | 45 | var nativeJson:Object; 46 | try { 47 | nativeJson = ApplicationDomain.currentDomain.getDefinition("JSON"); 48 | } catch (e:ReferenceError) { 49 | // ignore 50 | } 51 | 52 | const charConvert:ByteArray = new ByteArray(); 53 | charConvert.length = 0x100;// fill w/ 0's 54 | charConvert[0x22] = 0x22;// \" -> " 55 | charConvert[0x5c] = 0x5c;// \\ -> \ 56 | charConvert[0x2f] = 0x2f;// \/ -> / 57 | charConvert[0x62] = 0x8;// \b -> backspace 58 | charConvert[0x66] = 0xc;// \f -> formfeed 59 | charConvert[0x6e] = 0xa;// \n -> newline 60 | charConvert[0x72] = 0xd;// \r -> carriage return 61 | charConvert[0x74] = 0x9;// \t -> horizontal tab 62 | 63 | const isNumberChar:ByteArray = new ByteArray(); 64 | isNumberChar.length = 0x100;// fill w/ 0's 65 | isNumberChar[0x2b] = 1;// + 66 | isNumberChar[0x2d] = 1;// - 67 | isNumberChar[0x2e] = 1;// . 68 | isNumberChar[0x30] = 1;// 0 69 | isNumberChar[0x31] = 1;// 1 70 | isNumberChar[0x32] = 1;// 2 71 | isNumberChar[0x33] = 1;// 3 72 | isNumberChar[0x34] = 1;// 4 73 | isNumberChar[0x35] = 1;// 5 74 | isNumberChar[0x36] = 1;// 6 75 | isNumberChar[0x37] = 1;// 7 76 | isNumberChar[0x38] = 1;// 8 77 | isNumberChar[0x39] = 1;// 9 78 | isNumberChar[0x45] = 1;// E 79 | isNumberChar[0x65] = 1;// e 80 | 81 | // this is a trick to speed up the string parsing loop 82 | // 1 means go, 0 means stop 83 | const stringHelper:ByteArray = new ByteArray(); 84 | stringHelper.length = 0x100;// fill w/ 0's 85 | var i:int = 0; 86 | while (i < 0x100) { 87 | stringHelper[i++] = 1; 88 | } 89 | stringHelper[0x22] = 0;// " 90 | stringHelper[0x5c] = 0;// \ 91 | 92 | const isWhitespace:ByteArray = new ByteArray(); 93 | isWhitespace.length = 0x100;// fill w/ 0's 94 | isWhitespace[0x9] = 1;// \t 95 | isWhitespace[0xa] = 1;// \n 96 | isWhitespace[0xd] = 1;// \r 97 | isWhitespace[0x20] = 1;// " " 98 | 99 | const parseNumber:Function = function():Number { 100 | if (position === 1) { 101 | byteInput.position = 0; 102 | return parseFloat(byteInput.readUTFBytes(byteInput.length)); 103 | } else { 104 | byteInput.position = position - 1; 105 | while (isNumberChar[byteInput[position++]]) {} 106 | return Number(byteInput.readUTFBytes(position-- - byteInput.position - 1)); 107 | } 108 | }; 109 | 110 | const parseWhitespace:Function = function():Object { 111 | while (isWhitespace[byteInput[position]]) { 112 | position++; 113 | } 114 | return parse[byteInput[position++]](); 115 | }; 116 | 117 | const parseStringEscaped:Function = function(result:String):String { 118 | do { 119 | // which special character is it? 120 | if ((char = byteInput[position++]) === 0x75) {// \uxxxx -> utf8 char 121 | byteInput.position = position; 122 | char = parseInt(byteInput.readUTFBytes(4), 16); 123 | position += 4; 124 | } else if (!(char = charConvert[char])) { 125 | throw new Error( 126 | "Unknown escaped character encountered at position " + 127 | (position - 1)); 128 | } else { 129 | byteInput.position = position; 130 | } 131 | 132 | // write special character to result 133 | result += String.fromCharCode(char); 134 | 135 | while (stringHelper[byteInput[position++]]) {} 136 | 137 | // flush the buffered data to the result 138 | if ((position - 1) > byteInput.position) { 139 | result += byteInput.readUTFBytes((position - 1) - byteInput.position); 140 | } 141 | } while (byteInput[position - 1] === 0x5c);// == / 142 | 143 | return result; 144 | } 145 | 146 | // parse is a mapping of the first character of what's being parsed, to the 147 | // function that parses it 148 | const parse:Object = { 149 | 0x22: function ():String {// " 150 | if (stringHelper[byteInput[position++]]) { 151 | byteInput.position = position - 1; 152 | 153 | // this tight loop is intended for simple strings, parseStringEscaped 154 | // will handle the more advanced cases 155 | while (stringHelper[byteInput[position++]]) {} 156 | 157 | if (byteInput[position - 1] === 0x5c) {// == \ 158 | return parseStringEscaped( 159 | byteInput.readUTFBytes((position - 1) - byteInput.position)); 160 | } 161 | return byteInput.readUTFBytes((position - 1) - byteInput.position); 162 | } else if (byteInput[position - 1] === 0x5c) {// == \ 163 | return parseStringEscaped(""); 164 | } else { 165 | return ""; 166 | } 167 | }, 168 | 0x7b: function ():Object {// { 169 | while (isWhitespace[byteInput[position]]) { 170 | position++; 171 | } 172 | if (byteInput[position] === 0x7d) {// == } 173 | position++; 174 | return {}; 175 | } 176 | 177 | var result:Object = {}, key:String; 178 | do { 179 | do { 180 | key = parse[byteInput[position++]](); 181 | if (byteInput[position] !== 0x3a) {// != : 182 | while (isWhitespace[byteInput[position]]) { 183 | position++; 184 | } 185 | if (byteInput[position++] !== 0x3a) {// != : 186 | throw new Error("Expected : at " + (position - 1)); 187 | } 188 | } else { 189 | position++; 190 | } 191 | result[key] = parse[byteInput[position++]](); 192 | } while (byteInput[position++] === 0x2c);// == , 193 | if (byteInput[position - 1] === 0x7d) {// == } 194 | return result; 195 | } 196 | while (isWhitespace[byteInput[position]]) { 197 | position++; 198 | } 199 | } while (byteInput[position++] === 0x2c);// == , 200 | if (byteInput[position - 1] !== 0x7d) {// != } 201 | throw new Error("Expected , or } at " + (position - 1)); 202 | } 203 | return result; 204 | }, 205 | 0x5b: function ():Object {// [ 206 | while (isWhitespace[byteInput[position]]) { 207 | position++; 208 | } 209 | if (byteInput[position] === 0x5d) {// == ] 210 | position++; 211 | return []; 212 | } 213 | 214 | var result:Array = []; 215 | do { 216 | do { 217 | result[result.length] = parse[byteInput[position++]](); 218 | } while (byteInput[position++] === 0x2c);// == , 219 | if (byteInput[position - 1] === 0x5d) {// != ] 220 | return result; 221 | } 222 | position--; 223 | while (isWhitespace[byteInput[position]]) { 224 | position++; 225 | } 226 | } while (byteInput[position++] === 0x2c);// == , 227 | if (byteInput[position - 1] !== 0x5d) {// != ] 228 | throw new Error("Expected , or ] at " + (position - 1)); 229 | } 230 | return result; 231 | }, 232 | 0x74: function ():Boolean {// t 233 | if (byteInput[position] === 0x72 && 234 | byteInput[position + 1] === 0x75 && 235 | byteInput[position + 2] === 0x65) {// == rue 236 | position += 3; 237 | return true; 238 | } 239 | throw new Error("Expected \"true\" at position " + position); 240 | }, 241 | 0x66: function ():Boolean {// f 242 | if (byteInput[position] === 0x61 && 243 | byteInput[position + 1] === 0x6c && 244 | byteInput[position + 2] === 0x73 && 245 | byteInput[position + 3] === 0x65) {// == alse 246 | position += 4; 247 | return false; 248 | } 249 | throw new Error("Expected \"false\" at position " + (position - 1)); 250 | }, 251 | 0x6e: function ():Object {// n 252 | if (byteInput[position] === 0x75 && 253 | byteInput[position + 1] === 0x6c && 254 | byteInput[position + 2] === 0x6c) {// == ull 255 | position += 3; 256 | return null; 257 | } 258 | throw new Error("Expected \"null\" at position " + position); 259 | }, 260 | 0x5d: function ():void {// ] 261 | throw new Error("Unexpected end of array at " + position); 262 | }, 263 | 0x7d: function ():void {// } 264 | throw new Error("Unexpected end of object at " + position); 265 | }, 266 | 0x2c: function ():void {// , 267 | throw new Error("Unexpected comma at " + position); 268 | }, 269 | 0x2d: parseNumber,// - 270 | 0x30: parseNumber,// 0 271 | 0x31: parseNumber,// 1 272 | 0x32: parseNumber,// 2 273 | 0x33: parseNumber,// 3 274 | 0x34: parseNumber,// 4 275 | 0x35: parseNumber,// 5 276 | 0x36: parseNumber,// 6 277 | 0x37: parseNumber,// 7 278 | 0x38: parseNumber,// 8 279 | 0x39: parseNumber,// 9 280 | 0xd: parseWhitespace,// \r 281 | 0xa: parseWhitespace,// \n 282 | 0x9: parseWhitespace,// \t 283 | 0x20: parseWhitespace// " " 284 | }; 285 | 286 | return function (input:*, allowNativeJson:Boolean = false):Object { 287 | // prepare the input 288 | if (input is String) { 289 | if (nativeJson && allowNativeJson) { 290 | return nativeJson.parse(input); 291 | } else { 292 | byteInput = new ByteArray(); 293 | byteInput.writeUTFBytes(input as String); 294 | } 295 | } else if (input is ByteArray) { 296 | byteInput = input as ByteArray; 297 | } else { 298 | throw new Error("Unexpected input <" + input + ">"); 299 | } 300 | 301 | position = 0; 302 | 303 | try { 304 | return parse[byteInput[position++]](); 305 | } catch (e:TypeError) { 306 | if (position - 1 < byteInput.length) { 307 | e.message = "Unexpected character " + 308 | String.fromCharCode(byteInput[position - 1]) + 309 | " (0x" + byteInput[position - 1].toString(16) + ")" + 310 | " at position " + (position - 1) + " (" + e.message + ")"; 311 | } 312 | throw e; 313 | } 314 | return null; 315 | } 316 | } 317 | -------------------------------------------------------------------------------- /as3/com/brokenfunction/json/JsonDecoderAsync.as: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010 Maximilian Herkender 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.brokenfunction.json { 17 | import flash.errors.EOFError; 18 | import flash.events.IEventDispatcher; 19 | import flash.events.ProgressEvent; 20 | import flash.net.Socket; 21 | import flash.net.URLStream; 22 | import flash.utils.ByteArray; 23 | import flash.utils.IDataInput; 24 | 25 | /** 26 | * An asynchronous JSON decoder. 27 | * @see http://json.org/ 28 | */ 29 | public class JsonDecoderAsync { 30 | private static const _charConvert:ByteArray = new ByteArray(); 31 | 32 | private var _input:IDataInput; 33 | private var _result:* = undefined; 34 | private var _buffer:ByteArray = new ByteArray(); 35 | 36 | /** 37 | * All JSON objects have a terminating character, allowing this code to know 38 | * the end of a JSON object without an EOF. All, except numbers. If you use 39 | * JsonDecoderAsync for the kind of processsing where an EOF is not 40 | * available, you can change the value of this property so it will return an 41 | * error instead when it reaches a top level number. If not, the parser will 42 | * continue until a non-numeric ([-+eE.0-9]) is found. 43 | */ 44 | public var parseTopLevelNumbers:Boolean = true; 45 | 46 | /** 47 | * When parsing a top-level number, the parser will read past the last 48 | * character in the number. It is set here if this occurs, otherwise this 49 | * value is -1 50 | */ 51 | public var trailingByte:int = -1; 52 | 53 | /** 54 | * The _stack is pretty simple, the last number represents what is currently 55 | * being processed. 56 | *
 57 | 		 * -1 - arbitrary value needs parsing (object, string, etc)
 58 | 		 * 0-0xff - the starting character of what's to be parsed, "[" for arrays for example
 59 | 		 * 0x100-0x1ff - number-related parsing sections
 60 | 		 * 0x200-0x2ff - string-related parsing sections
 61 | 		 * 0x300-0x3ff - object-related parsing sections
 62 | 		 * 0x400-0x4ff - array-related parsing sections
 63 | 		 * 
64 | * 65 | * There's other things stored in the stack for objects and arrays 66 | * @private 67 | */ 68 | private var _stack:Array = [-1]; 69 | 70 | /** 71 | * Convert a String or ByteArray from an object to an a JSON-encoded string. 72 | * 73 | * This will automatically parse through. 74 | * 75 | * @parameter input An object to convert to JSON. 76 | * @parameter autoSubscribe If true, the decoder will attempt to subscribe 77 | * to any events that will provide more data, and start processing 78 | * immediately. Otherwise process() will have to be called manually. 79 | */ 80 | public function JsonDecoderAsync(input:*, autoSubscribe:Boolean = true):void { 81 | // prepare the input 82 | if (input is IDataInput) { 83 | _input = input as IDataInput; 84 | } else if (input is String) { 85 | var bytes:ByteArray = new ByteArray(); 86 | bytes.writeUTFBytes(input as String); 87 | bytes.position = 0; 88 | _input = bytes; 89 | } else { 90 | throw new Error("Unexpected input <" + input + ">"); 91 | } 92 | 93 | _input.endian = "bigEndian"; 94 | 95 | if (!_charConvert.length) { 96 | _charConvert.length = 0x100;// fill w/ 0's 97 | _charConvert[0x22] = 0x22;// \" -> " 98 | _charConvert[0x5c] = 0x5c;// \\ -> \ 99 | _charConvert[0x2f] = 0x2f;// \/ -> / 100 | _charConvert[0x62] = 0x8;// \b -> backspace 101 | _charConvert[0x66] = 0xc;// \f -> formfeed 102 | _charConvert[0x6e] = 0xa;// \n -> newline 103 | _charConvert[0x72] = 0xd;// \r -> carriage return 104 | _charConvert[0x74] = 0x9;// \t -> horizontal tab 105 | } 106 | 107 | if (autoSubscribe) { 108 | var dispatch:IEventDispatcher = input as IEventDispatcher; 109 | if (dispatch) { 110 | // dispatched by Sockets 111 | dispatch.addEventListener(ProgressEvent.SOCKET_DATA, progressHandler, false, 0, true); 112 | 113 | // dispatched by URLStreams 114 | dispatch.addEventListener(ProgressEvent.PROGRESS, progressHandler, false, 0, true); 115 | } 116 | process(); 117 | } 118 | } 119 | 120 | private function progressHandler(e:ProgressEvent):void { 121 | process(); 122 | } 123 | 124 | private function isWhitespace(char:int):Boolean { 125 | return (char === 0x20 || char === 0xd || char === 0xa || char === 0x9); 126 | // == " ", \r, \n, \t 127 | } 128 | 129 | /** 130 | * Returns an object if the JSON was fully parsed. 131 | * 132 | * If parseTopLevelNumbers is true, then it may contain partially parsed 133 | * numbers while parsing a top-level number. 134 | */ 135 | public function get result():* { 136 | return _result; 137 | } 138 | 139 | /** 140 | * Continue processing data. 141 | * 142 | * @property limit Stop processing after this many characters. Please note 143 | * that this is not a hard limit, only a rough limit. If limit is 0 it will 144 | * continue until an EOF is encountered. 145 | * @return True if processing is finished. If parseTopLevelNumbers is true 146 | * this function may return false even if there is no more data, since the 147 | * function can't know if more data is coming for top-level numbers. 148 | */ 149 | public function process(limit:uint = 0):Boolean { 150 | if (_stack.length <= 0) { 151 | return true; 152 | } 153 | 154 | var startAvailable:uint = _input.bytesAvailable; 155 | var result:Object, char:int; 156 | try { 157 | mainloop:while (_stack.length > 0) { 158 | switch (_stack[_stack.length - 1]) { 159 | case -1:// start parsing an unknown value 160 | _stack[_stack.length - 1] = _input.readUnsignedByte(); 161 | continue mainloop; 162 | 163 | case 0x22: // " (parse a string) 164 | if (limit > 0) { 165 | // limited version 166 | while ((char = _input.readUnsignedByte()) !== 0x22) {// != " 167 | if (char === 0x5c) {// == \ 168 | _stack[_stack.length - 1] = 0x200; 169 | continue mainloop; 170 | } else { 171 | _buffer.writeByte(char); 172 | } 173 | if (startAvailable - _input.bytesAvailable >= limit) { 174 | return false; 175 | } 176 | } 177 | } else { 178 | // unlimited version 179 | while ((char = _input.readUnsignedByte()) !== 0x22) {// != " 180 | if (char === 0x5c) {// == \ 181 | _stack[_stack.length - 1] = 0x200; 182 | continue mainloop; 183 | } else { 184 | _buffer.writeByte(char); 185 | } 186 | } 187 | } 188 | // string over 189 | _buffer.position = 0; 190 | result = _buffer.readUTFBytes(_buffer.length); 191 | _buffer.length = 0; 192 | _stack.pop(); 193 | continue mainloop; 194 | case 0x200:// parse the rest of a escape (\x) 195 | if ((char = _input.readUnsignedByte()) !== 0x75) {// \uxxxx 196 | char = _charConvert[char]; 197 | if (char !== 0) { 198 | _stack[_stack.length - 1] = 0x22; 199 | _buffer.writeByte(char); 200 | } else { 201 | throw new Error("Unexpected escape character"); 202 | } 203 | continue mainloop; 204 | } 205 | _stack[_stack.length - 1] = 0x201; 206 | case 0x201:// parse the rest of a unicode escape (\uxxxx) 207 | if (_input.bytesAvailable >= 4) { 208 | char = parseInt(_input.readUTFBytes(4), 16); 209 | // write the new character out as utf8 210 | if (char <= 0x7f) { 211 | _buffer.writeByte(char); 212 | } else if (char < 0x7ff) { 213 | _buffer.writeShort(0xC080 | 214 | ((char << 2) & 0x1f00) | (char & 0x3f)); 215 | } else { 216 | _buffer.writeByte(0xE0 | ((char >> 12) & 0xf)); 217 | _buffer.writeShort(0x8080 | 218 | ((char << 2) & 0x3f00) | (char & 0x3f)); 219 | } 220 | _stack[_stack.length - 1] = 0x22; 221 | continue mainloop; 222 | } 223 | return false; 224 | 225 | case 0x7b: // { (start parsing an object) 226 | if ((char = _input.readUnsignedByte()) === 0x7d) {// == } 227 | result = {}; 228 | _stack.pop(); 229 | } else { 230 | if (char === 0x22) {// == " 231 | _stack[_stack.length - 1] = {};// the object being created 232 | _stack[_stack.length] = null;// the current property being parsed 233 | _stack[_stack.length] = 0x300;// object parser, next stage 234 | _stack[_stack.length] = 0x22;// string parser 235 | } else if (isWhitespace(char)) { 236 | continue mainloop; 237 | } else { 238 | throw new Error("Unexpected character 0x" + 239 | char.toString(16) + " at the start of object"); 240 | } 241 | } 242 | continue mainloop; 243 | case 0x300:// key (string) has been parsed 244 | _stack[_stack.length - 2] = result; 245 | _stack[_stack.length - 1] = 0x301; 246 | case 0x301:// parse : and value 247 | if ((char = _input.readUnsignedByte()) !== 0x3a) {// == : 248 | if (isWhitespace(char)) { 249 | continue mainloop; 250 | } 251 | throw new Error("Expected : during object parsing, not 0x" + char.toString(16)); 252 | } 253 | _stack[_stack.length - 1] = 0x302; 254 | _stack[_stack.length] = -1; 255 | continue mainloop; 256 | case 0x302:// value has been parsed 257 | _stack[_stack.length - 3][_stack[_stack.length - 2]] = result; 258 | _stack[_stack.length - 1] = 0x303; 259 | case 0x303: // continue or end object 260 | if ((char = _input.readUnsignedByte()) === 0x2c) {// == , 261 | _stack[_stack.length - 1] = 0x304; 262 | if (limit > 0 && startAvailable - _input.bytesAvailable >= limit) { 263 | return false; 264 | } 265 | } else if (char === 0x7d) {// == } 266 | result = _stack[_stack.length - 3]; 267 | _stack.length -= 3; 268 | continue mainloop; 269 | } else if (isWhitespace(char)) { 270 | continue mainloop; 271 | } else { 272 | throw new Error("Expected , or } during object parsing, not 0x" + char.toString(16)); 273 | } 274 | case 0x304: // ensure next key is a string 275 | if ((char = _input.readUnsignedByte()) === 0x22) {// != " 276 | _stack[_stack.length - 1] = 0x300; 277 | _stack[_stack.length] = 0x22; 278 | } else if (isWhitespace(char)) { 279 | continue mainloop; 280 | } else { 281 | throw new Error("Expected \" during object parsing, not 0x" + char.toString(16)); 282 | } 283 | continue mainloop; 284 | 285 | case 0x5b: // [ (start parsing an array) 286 | if ((char = _input.readUnsignedByte()) === 0x5d) {// == ] 287 | result = []; 288 | _stack.pop(); 289 | continue mainloop; 290 | } else if (isWhitespace(char)) { 291 | continue mainloop; 292 | } 293 | _stack[_stack.length - 1] = [];// the array being created 294 | _stack[_stack.length] = 0x400;// object parser, next stage 295 | _stack[_stack.length] = char;// value parser 296 | continue mainloop; 297 | case 0x400:// value has been parsed 298 | (_stack[_stack.length - 2] as Array).push(result); 299 | _stack[_stack.length - 1] = 0x401; 300 | case 0x401:// continue or end array 301 | if ((char = _input.readUnsignedByte()) === 0x2c) {// == , 302 | _stack[_stack.length - 1] = 0x400; 303 | _stack[_stack.length] = -1; 304 | if (limit > 0 && startAvailable - _input.bytesAvailable >= limit) { 305 | return false; 306 | } 307 | } else if (char === 0x5d) {// == ] 308 | result = _stack[_stack.length - 2]; 309 | _stack.length -= 2; 310 | } else if (isWhitespace(char)) { 311 | continue mainloop; 312 | } else { 313 | throw new Error("Expected , or ] during array parsing, not 0x" + char.toString(16)); 314 | } 315 | continue mainloop; 316 | 317 | case 0x74: // t (start parsing true) 318 | if (_input.bytesAvailable >= 3) { 319 | if (_input.readShort() === 0x7275 && _input.readUnsignedByte() === 0x65) {// == rue 320 | result = true; 321 | _stack.pop(); 322 | continue mainloop; 323 | } else { 324 | throw new Error("Expected \"true\""); 325 | } 326 | } 327 | return false; 328 | case 0x66: // f (start parsing false) 329 | if (_input.bytesAvailable >= 4) { 330 | if (_input.readInt() === 0x616c7365) {// == alse 331 | result = false; 332 | _stack.pop(); 333 | continue mainloop; 334 | } else { 335 | throw new Error("Expected \"false\""); 336 | } 337 | } 338 | return false; 339 | case 0x6e: // n (start parsing null) 340 | if (_input.bytesAvailable >= 3) { 341 | if (_input.readShort() === 0x756c && _input.readUnsignedByte() === 0x6c) {// == ull 342 | result = null; 343 | _stack.pop(); 344 | continue mainloop; 345 | } else { 346 | throw new Error("Expected \"null\""); 347 | } 348 | } 349 | return false; 350 | 351 | case 0x100:// number parser when the number isn't the only value 352 | while ((char = _input.readUnsignedByte()) !== 0x5d && 353 | char !== 0x7d && char !== 0x2c) {// is it ",", "]", or "}"? 354 | _buffer.writeByte(char); 355 | } 356 | 357 | _buffer.position = 0; 358 | result = Number(_buffer.readUTFBytes(_buffer.length)); 359 | _buffer.length = 0; 360 | 361 | // have to do this here, since we've read too far into the input 362 | if (_stack[_stack.length - 2] === 0x302) {// an object is being parsed 363 | if (char === 0x2c) {// == , 364 | _stack[_stack.length - 4][_stack[_stack.length - 3]] = result; 365 | _stack.pop(); 366 | _stack[_stack.length - 1] = 0x304; 367 | } else if (char === 0x7d) {// == } 368 | _stack[_stack.length - 4][_stack[_stack.length - 3]] = result; 369 | result = _stack[_stack.length - 4]; 370 | _stack.length -= 4; 371 | } else { 372 | throw new Error("Unexpected ] while parsing object"); 373 | } 374 | } else if (_stack[_stack.length - 2] === 0x400) {// an array is being parsed 375 | if (char === 0x2c) {// == , 376 | (_stack[_stack.length - 3] as Array).push(result); 377 | _stack[_stack.length - 1] = -1; 378 | } else if (char === 0x5d) {// == ] 379 | (_stack[_stack.length - 3] as Array).push(result); 380 | result = _stack[_stack.length - 3]; 381 | _stack.length -= 3; 382 | } else { 383 | throw new Error("Unexpected } while parsing array"); 384 | } 385 | } 386 | continue mainloop; 387 | 388 | case 0xd:// \r 389 | case 0xa:// \n 390 | case 0x9:// \t 391 | case 0x20:// " " 392 | // skip whitespace 393 | while ((char = _input.readUnsignedByte()) === 0x20 ||// == " " 394 | char === 0xd || char === 0xa || char === 0x9) {};// == \r, \n, \t 395 | _stack[_stack.length - 1] = char; 396 | break; 397 | 398 | case 0x101:// number parser when the number is the only value 399 | while (_input.bytesAvailable) { 400 | if (((char = _input.readUnsignedByte()) >= 0x30 && char <= 0x39) || 401 | char === 0x65 || char === 0x45 || char === 0x2e || 402 | char === 0x2b || char === 0x2d) {// 0-9, e, E, ., +, - 403 | _buffer.writeByte(char); 404 | } else { 405 | trailingByte = char; 406 | _buffer.position = 0; 407 | result = Number(_buffer.readUTFBytes(_buffer.length)); 408 | _buffer.length = 0; 409 | _stack.pop(); 410 | continue mainloop; 411 | } 412 | } 413 | // parse the current number anyway, in case this is the end 414 | _buffer.position = 0; 415 | _result = Number(_buffer.readUTFBytes(_buffer.length)); 416 | return false; 417 | 418 | default: 419 | // test for the other number cases, otherwise it's invalid 420 | char = _stack[_stack.length - 1]; 421 | if (char === 0x2d || (char >= 0x30 && char <= 0x39)) {// - or 0-9 422 | if (_stack.length <= 1) { 423 | if (parseTopLevelNumbers) { 424 | _stack[_stack.length - 1] = 0x101; 425 | } else { 426 | throw new Error("Top level number encountered"); 427 | } 428 | } else { 429 | _stack[_stack.length - 1] = 0x100; 430 | } 431 | _buffer.writeByte(char); 432 | continue mainloop; 433 | } else { 434 | throw new Error("Unexpected character 0x" + char.toString(16) + ", expecting a value"); 435 | } 436 | break; 437 | } 438 | } 439 | } catch (e:EOFError) { 440 | return false; 441 | } catch (e:Error) { 442 | // Parsing failed, nothing left to do 443 | _stack.length = 0; 444 | throw e; 445 | } 446 | if (_stack.length <= 0) { 447 | _result = result; 448 | return true; 449 | } else { 450 | return false; 451 | } 452 | } 453 | } 454 | } 455 | -------------------------------------------------------------------------------- /as3/com/brokenfunction/json/TestJson.as: -------------------------------------------------------------------------------- 1 | package com.brokenfunction.json { 2 | import flash.display.Sprite; 3 | import flash.utils.ByteArray; 4 | import flash.utils.getTimer; 5 | 6 | import mx.utils.ObjectUtil; 7 | 8 | import com.adobe.serialization.json.JSONDecoder; 9 | import com.adobe.serialization.json.JSONEncoder; 10 | 11 | // conflicts with native JSON parser now 12 | //import com.rational.serialization.json.JSON; 13 | 14 | public class TestJson extends Sprite { 15 | public function TestJson() { 16 | checkDecode("true"); 17 | checkDecode("false"); 18 | checkDecode("null"); 19 | checkDecode("\"string\""); 20 | checkDecode("\"\""); 21 | checkDecode("\"\\\"\\\\\\/\\b\\f\\n\\r\\t\"", "\"\\\"\\\\/\\b\\f\\n\\r\\t\""); 22 | checkDecode("\"\\\"x\\\\x\\/x\\bx\\fx\\nx\\rx\\t\"", "\"\\\"x\\\\x/x\\bx\\fx\\nx\\rx\\t\""); 23 | checkDecode("\"test\\u0021unicode\"", "\"test!unicode\""); 24 | checkDecode("\"test\\u222Bunicode\\u222b\"", "\"test\u222Bunicode\u222b\""); 25 | checkDecode("\"test\\u00f7unicode\"", "\"test\u00f7unicode\""); 26 | checkDecode("\"\\u0021unicode\"", "\"\u0021unicode\""); 27 | checkDecode("\"unicode\\u0021\"", "\"unicode\u0021\""); 28 | checkDecode("123", "123", true); 29 | checkDecode("123e1", "1230", true); 30 | checkDecode("0.1", "0.1", true); 31 | checkDecode("123.1", "123.1", true); 32 | checkDecode("-123.1", "-123.1", true); 33 | checkDecode("123e1", "1230", true); 34 | checkDecode("123e+1", "1230", true); 35 | checkDecode("123e-1", "12.3", true); 36 | checkDecode("123E-1", "12.3", true); 37 | checkDecode("123e-001", "12.3", true); 38 | checkDecode("[]"); 39 | checkDecode("[\r\n\t ]","[]"); 40 | checkDecode("[\"string\",null,true,false,1]"); 41 | checkDecode( 42 | "[ \"string\" ,\rnull\r,\ntrue\n,\tfalse\t,\r1\r]", 43 | "[\"string\",null,true,false,1]"); 44 | checkDecode("[\"test\\u0021\"]", "[\"test\u0021\"]"); 45 | checkDecode("{}"); 46 | checkDecode("{\r\n\t }","{}"); 47 | checkDecode("{\"test\":{\"test\":{\"test\":\"sdfsdf\"}}}"); 48 | checkDecode( 49 | "{\r\"test\"\r:\n{\n\"test\"\t:\t{ \"test\" :\r\"sdfsdf\"\n}\t} }", 50 | "{\"test\":{\"test\":{\"test\":\"sdfsdf\"}}}"); 51 | checkDecode("{\"test\":\"sdfsdf\",\"test\":\"sdfsdf\"}", "{\"test\":\"sdfsdf\"}"); 52 | checkDecode("[\"test\",43243,{\"test\":\"sdfsdf\"},4343]"); 53 | checkDecode("[\"string\",null,true,false,1,{\"string\":\"string\"}]"); 54 | checkDecodeMulti("{\"test\":\"sdfsdf\",\"test2\":\"sdfsdf\"}", [ 55 | "{\"test\":\"sdfsdf\",\"test2\":\"sdfsdf\"}", 56 | "{\"test2\":\"sdfsdf\",\"test\":\"sdfsdf\"}"]); 57 | checkDecodeMulti("{\"a\":143,\"b\":232}", [ 58 | "{\"a\":143,\"b\":232}","{\"b\":232,\"a\":143}"]); 59 | checkDecodeMulti("{\"a\":\"test\",\"b\":2}", [ 60 | "{\"a\":\"test\",\"b\":2}","{\"b\":1,\"a\":\"test\"}"]); 61 | checkDecodeMulti("{\"a\":1,\"b\":\"test\"}", [ 62 | "{\"a\":1,\"b\":\"test\"}","{\"b\":\"test\",\"a\":1}"]); 63 | checkDecodeMulti("{\"a\":234,\"c\":[1,2,3,242342298e10,-1235],\"d\":[{\"a\":\"test\",\"b\":\"test\"}]}",[ 64 | "{\"d\":[{\"b\":\"test\",\"a\":\"test\"}],\"c\":[1,2,3,242342298e10,-1235],\"a\":234}"]); 65 | checkEncode(true); 66 | checkEncode(false); 67 | checkEncode(null); 68 | checkEncode("string"); 69 | checkEncode("\"\\/\b\f\n\r\t"); 70 | checkEncode("\u0021"); 71 | checkEncode("\u222B"); 72 | checkEncode("\u00f7"); 73 | checkEncode("x\u222Bx"); 74 | checkEncode(123); 75 | checkEncode(uint.MAX_VALUE); 76 | checkEncode(int.MIN_VALUE); 77 | checkEncode(Number.MAX_VALUE); 78 | checkEncode(Number.MIN_VALUE); 79 | checkEncode([]); 80 | checkEncode(["test"]); 81 | checkEncode([true, false]); 82 | checkEncode([0, 1, 2]); 83 | checkEncode(["string",null,true,false,1,{"string":"string"}]); 84 | checkEncode({}); 85 | checkEncode({"test": "test"}); 86 | checkEncode({"a": 1, "b": 2}); 87 | checkEncode({"a": 1, "b": 2, "c": 3}); 88 | checkEncode({ 89 | a: 234, 90 | c: [1, 2, 3, 242342298e10, -1235], 91 | d: [{a: "test", b: "test"}] 92 | }); 93 | checkEncode( 94 | "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f" + 95 | "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" + 96 | "\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f" + 97 | "\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f" + 98 | "\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f" + 99 | "\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f" + 100 | "\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f" + 101 | "\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f", 102 | "\"\\u0000\\u0001\\u0002\\u0003\\u0004\\u0005\\u0006\\u0007\\b\\t\\n\\u000B\\f\\r" + 103 | "\\u000E\\u000F\\u0010\\u0011\\u0012\\u0013\\u0014\\u0015\\u0016\\u0017\\u0018" + 104 | "\\u0019\\u001A\\u001B\\u001C\\u001D\\u001E\\u001F !\\\"#$%&'()*+,-./0123456789" + 105 | ":;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\\u007F\""); 106 | checkEncode(, "\"\""); 107 | checkEncode(
  1. world><
, "\"\\n
    \\n
  1. <\\\">world><
  2. \\n
\\n
\""); 108 | checkEncode(
  • .attribute("alt"), "\"world><\""); 109 | 110 | trace("All tests passed"); 111 | 112 | testDecode( 113 | "true", 114 | function (data:String):void { 115 | for (var i:int = 0; i < 30000; i++) (new JSONDecoder(data)).getValue();}, 116 | function (data:String):void { 117 | for (var i:int = 0; i < 30000; i++) decodeJson(data);}, 118 | function (data:String):void { 119 | for (var i:int = 0; i < 30000; i++) (new JsonDecoderAsync(data)).result;}, 120 | function (data:ByteArray):void { 121 | for (var i:int = 0; i < 30000; i++) decodeJson(data);}, 122 | function (data:String):void { 123 | for (var i:int = 0; i < 30000; i++) JSON.parse(data);} 124 | ); 125 | testDecode( 126 | "\"test string\"", 127 | function (data:String):void { 128 | for (var i:int = 0; i < 30000; i++) (new JSONDecoder(data)).getValue();}, 129 | function (data:String):void { 130 | for (var i:int = 0; i < 30000; i++) decodeJson(data);}, 131 | function (data:String):void { 132 | for (var i:int = 0; i < 30000; i++) (new JsonDecoderAsync(data)).result;}, 133 | function (data:ByteArray):void { 134 | for (var i:int = 0; i < 30000; i++) decodeJson(data);}, 135 | function (data:String):void { 136 | for (var i:int = 0; i < 30000; i++) JSON.parse(data);} 137 | ); 138 | testDecode( 139 | "-123e5", 140 | function (data:String):void { 141 | for (var i:int = 0; i < 30000; i++) (new JSONDecoder(data)).getValue();}, 142 | function (data:String):void { 143 | for (var i:int = 0; i < 30000; i++) decodeJson(data);}, 144 | function (data:String):void { 145 | for (var i:int = 0; i < 30000; i++) (new JsonDecoderAsync(data)).result;}, 146 | function (data:ByteArray):void { 147 | for (var i:int = 0; i < 30000; i++) decodeJson(data);}, 148 | function (data:String):void { 149 | for (var i:int = 0; i < 30000; i++) JSON.parse(data);} 150 | ); 151 | testDecode( 152 | "[1,2,3]", 153 | function (data:String):void { 154 | for (var i:int = 0; i < 10000; i++) (new JSONDecoder(data)).getValue();}, 155 | function (data:String):void { 156 | for (var i:int = 0; i < 10000; i++) decodeJson(data);}, 157 | function (data:String):void { 158 | for (var i:int = 0; i < 10000; i++) (new JsonDecoderAsync(data)).result;}, 159 | function (data:ByteArray):void { 160 | for (var i:int = 0; i < 10000; i++) decodeJson(data);}, 161 | function (data:String):void { 162 | for (var i:int = 0; i < 10000; i++) JSON.parse(data);} 163 | ); 164 | testDecode( 165 | "{\"test\":\"sdfsdf\",\"test2\":\"sdfsdf\"}", 166 | function (data:String):void { 167 | for (var i:int = 0; i < 10000; i++) (new JSONDecoder(data)).getValue();}, 168 | function (data:String):void { 169 | for (var i:int = 0; i < 10000; i++) decodeJson(data);}, 170 | function (data:String):void { 171 | for (var i:int = 0; i < 10000; i++) (new JsonDecoderAsync(data)).result;}, 172 | function (data:ByteArray):void { 173 | for (var i:int = 0; i < 10000; i++) decodeJson(data);}, 174 | function (data:String):void { 175 | for (var i:int = 0; i < 10000; i++) JSON.parse(data);} 176 | ); 177 | testDecode( 178 | encodeJson({ 179 | a: 234, 180 | b: [{a: "x", b: "test string\r\ntest string"}], 181 | c: [1, 2, 3, 242342298e10, -1235], 182 | d: [ 183 | {a: "test", b: "test"},{a: "test", b: "test"}, 184 | {a: "test", b: "test"},{a: "test", b: "test"}, 185 | {a: "test", b: "test"},{a: "test", b: "test"}, 186 | {a: "test", b: "test"},{a: "test", b: "test"}, 187 | {a: "test", b: "test"},{a: "test", b: "test"}, 188 | {a: "test", b: "test"},{a: "test", b: "test"}] 189 | }), 190 | function (data:String):void { 191 | for (var i:int = 0; i < 1000; i++) (new JSONDecoder(data)).getValue();}, 192 | function (data:String):void { 193 | for (var i:int = 0; i < 1000; i++) decodeJson(data);}, 194 | function (data:String):void { 195 | for (var i:int = 0; i < 1000; i++) (new JsonDecoderAsync(data)).result;}, 196 | function (data:ByteArray):void { 197 | for (var i:int = 0; i < 1000; i++) decodeJson(data);}, 198 | function (data:String):void { 199 | for (var i:int = 0; i < 1000; i++) JSON.parse(data);} 200 | ); 201 | testEncode( 202 | true, 203 | function (data:Object):void { 204 | for (var i:int = 0; i < 100000; i++) (new JSONEncoder(data)).getString();}, 205 | function (data:Object):void { 206 | for (var i:int = 0; i < 100000; i++) encodeJson(data);}, 207 | function (data:Object):void { 208 | for (var i:int = 0; i < 100000; i++) JSON.stringify(data);}, 209 | function (data:Object):void { 210 | for (var i:int = 0; i < 100000; i++) (new JsonEncoderAsync(data)).result;} 211 | ); 212 | testEncode( 213 | -123e4, 214 | function (data:Object):void { 215 | for (var i:int = 0; i < 100000; i++) (new JSONEncoder(data)).getString();}, 216 | function (data:Object):void { 217 | for (var i:int = 0; i < 100000; i++) encodeJson(data);}, 218 | function (data:Object):void { 219 | for (var i:int = 0; i < 100000; i++) JSON.stringify(data);}, 220 | function (data:Object):void { 221 | for (var i:int = 0; i < 100000; i++) (new JsonEncoderAsync(data)).result;} 222 | ); 223 | testEncode( 224 | "this is a string", 225 | function (data:Object):void { 226 | for (var i:int = 0; i < 100000; i++) (new JSONEncoder(data)).getString();}, 227 | function (data:Object):void { 228 | for (var i:int = 0; i < 100000; i++) encodeJson(data);}, 229 | function (data:Object):void { 230 | for (var i:int = 0; i < 100000; i++) JSON.stringify(data);}, 231 | function (data:Object):void { 232 | for (var i:int = 0; i < 100000; i++) (new JsonEncoderAsync(data)).result;} 233 | ); 234 | testEncode( 235 | "this is a much longer string to understand the effect the size of the string has on the results", 236 | function (data:Object):void { 237 | for (var i:int = 0; i < 10000; i++) (new JSONEncoder(data)).getString();}, 238 | function (data:Object):void { 239 | for (var i:int = 0; i < 10000; i++) encodeJson(data);}, 240 | function (data:Object):void { 241 | for (var i:int = 0; i < 10000; i++) JSON.stringify(data);}, 242 | function (data:Object):void { 243 | for (var i:int = 0; i < 10000; i++) (new JsonEncoderAsync(data)).result;} 244 | ); 245 | testEncode( 246 | [Number.MIN_VALUE, "test", null], 247 | function (data:Object):void { 248 | for (var i:int = 0; i < 10000; i++) (new JSONEncoder(data)).getString();}, 249 | function (data:Object):void { 250 | for (var i:int = 0; i < 10000; i++) encodeJson(data);}, 251 | function (data:Object):void { 252 | for (var i:int = 0; i < 10000; i++) JSON.stringify(data);}, 253 | function (data:Object):void { 254 | for (var i:int = 0; i < 10000; i++) (new JsonEncoderAsync(data)).result;} 255 | ); 256 | testEncode( 257 | {a:12345, b:null}, 258 | function (data:Object):void { 259 | for (var i:int = 0; i < 30000; i++) (new JSONEncoder(data)).getString();}, 260 | function (data:Object):void { 261 | for (var i:int = 0; i < 30000; i++) encodeJson(data);}, 262 | function (data:Object):void { 263 | for (var i:int = 0; i < 30000; i++) JSON.stringify(data);}, 264 | function (data:Object):void { 265 | for (var i:int = 0; i < 30000; i++) (new JsonEncoderAsync(data)).result;} 266 | ); 267 | testEncode( 268 | { 269 | a: 234, 270 | b: [{a: "x", b: "test string\r\ntest string"}], 271 | c: [1, 2, 3, 242342298e10, -1235], 272 | d: [ 273 | {a: "test", b: "test"},{a: "test", b: "test"}, 274 | {a: "test", b: "test"},{a: "test", b: "test"}, 275 | {a: "test", b: "test"},{a: "test", b: "test"}, 276 | {a: "test", b: "test"},{a: "test", b: "test"}, 277 | {a: "test", b: "test"},{a: "test", b: "test"}, 278 | {a: "test", b: "test"},{a: "test", b: "test"}] 279 | }, 280 | function (data:Object):void { 281 | for (var i:int = 0; i < 1000; i++) (new JSONEncoder(data)).getString();}, 282 | function (data:Object):void { 283 | for (var i:int = 0; i < 1000; i++) encodeJson(data);}, 284 | function (data:Object):void { 285 | for (var i:int = 0; i < 1000; i++) JSON.stringify(data);}, 286 | function (data:Object):void { 287 | for (var i:int = 0; i < 1000; i++) (new JsonEncoderAsync(data)).result;} 288 | ); 289 | } 290 | 291 | public function checkDecode(input:String, expectedResult:String = "", isNumber:Boolean = false):void { 292 | if (!expectedResult) { 293 | expectedResult = input; 294 | } 295 | checkDecode2(input, expectedResult, isNumber); 296 | 297 | // trailing characters should be ignored 298 | checkDecode3(input, "\"", expectedResult, isNumber); 299 | checkDecode3(input, "]", expectedResult, isNumber); 300 | checkDecode3(input, "}x8!", expectedResult, isNumber); 301 | 302 | // try combining it with something else 303 | checkDecode2("{\"test\":" + input + "}", "{\"test\":" + expectedResult + "}", false); 304 | checkDecode2("\n\r\t " + input + "\n\r\t ", expectedResult, false); 305 | checkDecode2("[[2],[[[" + input + "]],[5]]]", "[[2],[[[" + expectedResult + "]],[5]]]", false); 306 | checkDecode2("{\"a\":{\"b\":{\"c\":" + input + "}}}", "{\"a\":{\"b\":{\"c\":" + expectedResult + "}}}", false); 307 | checkDecode2("[" + input + "]", "[" + expectedResult + "]", false); 308 | checkDecode2("[\"x\"," + input + ",54564]", "[\"x\"," + expectedResult + ",54564]", false); 309 | checkDecode2("[" + input + "," + input + "]", "[" + expectedResult + "," + expectedResult + "]", false); 310 | checkDecode2("[\n\r\t " + input + "\n\r\t ,\n\r\t " + input + "\n\r\t ]", "[" + expectedResult + "," + expectedResult + "]", false); 311 | checkDecode2("{\"a\":{\"b\":{\"c\":\n\r\t " + input + "\n\r\t }}}", "{\"a\":{\"b\":{\"c\":" + expectedResult + "}}}", false); 312 | checkDecode2("{ \"a\" : " + input + " , \"b\": [" + input + "] }", "{\"b\":[" + expectedResult + "],\"a\":" + expectedResult + "}", false); 313 | } 314 | 315 | public function checkDecodeMulti(input:String, possibleResults:Array):void { 316 | var i:int = possibleResults.length; 317 | var errors:String; 318 | while (i-- > 0) { 319 | try { 320 | checkDecode(input, possibleResults[i]); 321 | return; 322 | } catch (e:Error) { 323 | errors += e.getStackTrace() + "\n\n"; 324 | continue; 325 | } 326 | } 327 | } 328 | 329 | public function checkDecode2(input:String, expectedResult:String, isNumber:Boolean):void { 330 | // try the fast decoder 331 | try { 332 | trace(input+" -> " + expectedResult); 333 | var result:Object = decodeJson(input); 334 | var adobeResult:Object = (new JSONEncoder(expectedResult)).getString(); 335 | if (ObjectUtil.compare(result, adobeResult) == 0) { 336 | throw new Error("Result: " + adobeResult); 337 | } 338 | } catch (e:Error) { 339 | throw new Error("JSONEncoder(decodeJson()) failed: " + input + " -> " + expectedResult + "\n" + e.getStackTrace() + "\n\n"); 340 | } 341 | 342 | // try the fast decoder, with optional native JSON support 343 | try { 344 | trace(input+" -> " + expectedResult + " (w/ optional native JSON)"); 345 | result = decodeJson(input, true); 346 | adobeResult = (new JSONEncoder(expectedResult)).getString(); 347 | if (ObjectUtil.compare(result, adobeResult) == 0) { 348 | throw new Error("Result: " + adobeResult); 349 | } 350 | } catch (e:Error) { 351 | throw new Error("JSONEncoder(decodeJson()) (w/ native JSON) failed: " + input + " -> " + expectedResult + "\n" + e.getStackTrace() + "\n\n"); 352 | } 353 | 354 | 355 | // try the async decoder 356 | try { 357 | trace(input+" -> " + expectedResult + " (async)"); 358 | result = (new JsonDecoderAsync(input)).result; 359 | adobeResult = (new JSONEncoder(expectedResult)).getString(); 360 | if (ObjectUtil.compare(result, adobeResult) == 0) { 361 | throw new Error("Result: " + adobeResult); 362 | } 363 | } catch (e:Error) { 364 | throw new Error("JSONEncoder(JsonDecoderAsync()) failed: " + input + " -> " + expectedResult + "\n" + e.getStackTrace() + "\n\n"); 365 | } 366 | 367 | // try the async decoder, but only give one byte at a time 368 | try { 369 | trace(input+" -> " + expectedResult + " (async 2)"); 370 | var bytes:ByteArray = new ByteArray(); 371 | bytes.writeUTFBytes(input); 372 | bytes.position = 0; 373 | var bytes2:ByteArray = new ByteArray(); 374 | 375 | var asyncDecoder:JsonDecoderAsync = new JsonDecoderAsync(bytes2, false); 376 | while (bytes.position < bytes.length) { 377 | bytes.readBytes(bytes2, bytes2.length, 1); 378 | asyncDecoder.process(); 379 | } 380 | 381 | result = asyncDecoder.result; 382 | adobeResult = (new JSONEncoder(expectedResult)).getString(); 383 | if (ObjectUtil.compare(result, adobeResult) == 0) { 384 | throw new Error("Result: " + adobeResult); 385 | } 386 | } catch (e:Error) { 387 | throw new Error("JSONEncoder(JsonDecoderAsync()) (socket version) failed: " + input + " -> " + expectedResult + "\n" + e.getStackTrace() + "\n\n"); 388 | } 389 | 390 | // try the async decoder, but use limit 391 | if (!isNumber) { 392 | try { 393 | trace(input+" -> " + expectedResult + " (async 3)"); 394 | asyncDecoder = new JsonDecoderAsync(input, false); 395 | while (!asyncDecoder.process(3)) {}; 396 | adobeResult = (new JSONEncoder(expectedResult)).getString(); 397 | if (ObjectUtil.compare(asyncDecoder.result, adobeResult) == 0) { 398 | throw new Error("Result: " + adobeResult); 399 | } 400 | } catch (e:Error) { 401 | throw new Error("JSONEncoder(JsonDecoderAsync()) (limit version) failed: " + input + " -> " + expectedResult + "\n" + e.getStackTrace() + "\n\n"); 402 | } 403 | } 404 | } 405 | 406 | public function checkDecode3(input:String, extraChars:String, expectedResult:String, isNumber:Boolean):void { 407 | // try the async decoder, but make sure it doesn't read past the end of the string 408 | try { 409 | trace(input+" -> " + expectedResult + " (async extra chars)"); 410 | var bytes:ByteArray = new ByteArray(); 411 | bytes.writeUTFBytes(input + extraChars); 412 | bytes.position = 0; 413 | var bytes2:ByteArray = new ByteArray(); 414 | 415 | var asyncDecoder:JsonDecoderAsync = new JsonDecoderAsync(bytes2, false); 416 | while (bytes.position < bytes.length) { 417 | bytes.readBytes(bytes2, bytes2.length, 1); 418 | asyncDecoder.process(); 419 | } 420 | 421 | var result:Object = asyncDecoder.result; 422 | var adobeResult:String = (new JSONEncoder(expectedResult)).getString(); 423 | if (ObjectUtil.compare(asyncDecoder.result, adobeResult) == 0) { 424 | throw new Error("Result: " + adobeResult); 425 | } 426 | if (!isNumber && bytes2.position != input.length) { 427 | throw new Error("JsonDecoderAsync read past the end of the string, " + input.length + " to " + bytes2.position); 428 | } 429 | } catch (e:Error) { 430 | throw new Error("JSONEncoder(JsonDecoderAsync()) (extra chars) failed: " + input + extraChars + " -> " + expectedResult + "\n" + e.getStackTrace() + "\n\n"); 431 | } 432 | } 433 | 434 | public function checkEncode(input:Object, expectedResult:String = ""):void { 435 | if (!expectedResult) { 436 | expectedResult = (new JSONEncoder(input)).getString();; 437 | } 438 | checkEncode2(input, expectedResult); 439 | checkEncode2({"test":input}, "{\"test\":" + expectedResult + "}"); 440 | checkEncode2([input], "[" + expectedResult + "]"); 441 | checkEncode2([[],[9,9],[[[input]],[]]], "[[],[9,9],[[[" + expectedResult + "]],[]]]"); 442 | checkEncode2(["x",input,54564], "[\"x\"," + expectedResult + ",54564]"); 443 | checkEncode2([input,input],"[" + expectedResult + "," + expectedResult + "]"); 444 | } 445 | 446 | public function checkEncode2(input:Object, expectedResult:String):void { 447 | // try the fast encoder 448 | try { 449 | trace("encodeJson(" + expectedResult + ")"); 450 | var result:String = encodeJson(input); 451 | if (expectedResult != result) { 452 | throw new Error("Result: " + result); 453 | } 454 | } catch (e:Error) { 455 | throw new Error("encodeJson(" + expectedResult + ") failed\n" + e.getStackTrace() + "\n\n"); 456 | } 457 | 458 | // try the fast encode w/ optional native JSON 459 | try { 460 | trace("encodeJson(" + expectedResult + ") (w/ optional native JSON)"); 461 | result = encodeJson(input, null, false, true); 462 | if (expectedResult != result) { 463 | throw new Error("Result: " + result); 464 | } 465 | } catch (e:Error) { 466 | throw new Error("encodeJson(" + expectedResult + ") (w/ optional native JSON) failed\n" + e.getStackTrace() + "\n\n"); 467 | } 468 | 469 | // try the async encoder 470 | try { 471 | trace("JsonEncoderAsync(" + expectedResult + ")"); 472 | result = (new JsonEncoderAsync(input)).result; 473 | if (expectedResult != result) { 474 | throw new Error("Result: " + result); 475 | } 476 | } catch (e:Error) { 477 | throw new Error("JsonEncoderAsync(" + expectedResult + ") failed\n" + e.getStackTrace() + "\n\n"); 478 | } 479 | 480 | // try the async encoder, but limit it 481 | try { 482 | trace("JsonEncoderAsync(" + expectedResult + ") (limited)"); 483 | var asyncDecoder:JsonEncoderAsync = new JsonEncoderAsync(input); 484 | var chunks:int = 0; 485 | while (!asyncDecoder.process(1)) { 486 | chunks++; 487 | }; 488 | trace("Note: " + chunks + " chunks"); 489 | result = asyncDecoder.result; 490 | if (expectedResult != result) { 491 | throw new Error("Result: " + result); 492 | } 493 | } catch (e:Error) { 494 | throw new Error("JsonEncoderAsync(" + expectedResult + ") failed\n" + e.getStackTrace() + "\n\n"); 495 | } 496 | } 497 | 498 | public function testDecode(data:String, adobeTest:Function, fastTest:Function, asyncTest:Function, fastTest2:Function, nativeJsonTest:Function):void { 499 | var time:uint; 500 | var resultAdobe:uint = 0; 501 | var resultFast:uint = 0; 502 | var resultAsync:uint = 0; 503 | var resultFast2:uint = 0; 504 | var resultNativeJson:uint = 0; 505 | 506 | var dataBytes:ByteArray = new ByteArray(); 507 | dataBytes.writeUTFBytes(data); 508 | 509 | time = getTimer(); 510 | adobeTest(data); 511 | resultAdobe += getTimer() - time; 512 | 513 | time = getTimer(); 514 | fastTest(data); 515 | resultFast += getTimer() - time; 516 | 517 | time = getTimer(); 518 | asyncTest(data); 519 | resultAsync += getTimer() - time; 520 | 521 | time = getTimer(); 522 | nativeJsonTest(data); 523 | resultNativeJson += getTimer() - time; 524 | 525 | time = getTimer(); 526 | fastTest2(dataBytes); 527 | resultFast2 += getTimer() - time; 528 | 529 | time = getTimer(); 530 | asyncTest(data); 531 | resultAsync += getTimer() - time; 532 | 533 | time = getTimer(); 534 | fastTest(data); 535 | resultFast += getTimer() - time; 536 | 537 | time = getTimer(); 538 | fastTest2(dataBytes); 539 | resultFast2 += getTimer() - time; 540 | 541 | time = getTimer(); 542 | adobeTest(data); 543 | resultAdobe += getTimer() - time; 544 | 545 | time = getTimer(); 546 | nativeJsonTest(data); 547 | resultNativeJson += getTimer() - time; 548 | 549 | trace(""); 550 | trace("Decoding results for " + data); 551 | trace("decodeJson improvement: " + (Math.floor(100 * resultAdobe / resultFast) / 100) + "x"); 552 | trace("decodeJson improvement (w/o String overhead): " + (Math.floor(100 * resultAdobe / resultFast2) / 100) + "x"); 553 | trace("JsonDecoderAsync improvement: " + (Math.floor(100 * resultAdobe / resultAsync) / 100) + "x"); 554 | trace("Native JSON improvement: " + (Math.floor(100 * resultAdobe / resultNativeJson) / 100) + "x"); 555 | } 556 | 557 | public function testEncode(data:Object, adobeTest:Function, fastTest:Function, nativeJsonTest:Function, asyncTest:Function):void { 558 | var time:uint; 559 | var resultAdobe:uint = 0; 560 | var resultFast:uint = 0; 561 | var resultNativeJson:uint = 0; 562 | var resultAsync:uint = 0; 563 | 564 | time = getTimer(); 565 | adobeTest(data); 566 | resultAdobe += getTimer() - time; 567 | 568 | time = getTimer(); 569 | asyncTest(data); 570 | resultAsync += getTimer() - time; 571 | 572 | time = getTimer(); 573 | nativeJsonTest(data); 574 | resultNativeJson += getTimer() - time; 575 | 576 | time = getTimer(); 577 | fastTest(data); 578 | resultFast += getTimer() - time; 579 | 580 | time = getTimer(); 581 | adobeTest(data); 582 | resultAdobe += getTimer() - time; 583 | 584 | time = getTimer(); 585 | fastTest(data); 586 | resultFast += getTimer() - time; 587 | 588 | time = getTimer(); 589 | nativeJsonTest(data); 590 | resultNativeJson += getTimer() - time; 591 | 592 | time = getTimer(); 593 | asyncTest(data); 594 | resultAsync += getTimer() - time; 595 | 596 | trace(""); 597 | trace("Encoding results for " + encodeJson(data)); 598 | trace("encodeJson improvement: " + (Math.floor(100 * resultAdobe / resultFast) / 100) + "x"); 599 | trace("JsonDecoderAsync improvement: " + (Math.floor(100 * resultAdobe / resultAsync) / 100) + "x"); 600 | trace("Native JSON improvement: " + (Math.floor(100 * resultAdobe / resultNativeJson) / 100) + "x"); 601 | } 602 | } 603 | } 604 | --------------------------------------------------------------------------------