├── 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(- world><
, "\"\\n \\n - <\\\">world><
\\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 |
--------------------------------------------------------------------------------