/** * com.sekati.data.JSON * @version 1.0.3 * @author jason m horwitz | sekati.com * Copyright (C) 2007 jason m horwitz, Sekat LLC. All Rights Reserved. * Released under the MIT License: http://www.opensource.org/licenses/mit-license.php * * Adapted from cinqetdemi.JSON for dependencies only. */ import com.sekati.core.CoreObject; /** * JSON (JavaScript Object Notation) Serializing/Deserializing Parser. * * {@code Usage: * var JSONObj:Object = {unlucky:13, lucky:7, household:{ people:['Jason','Therese'], pets:['Finn','Van'] }, colors:{ black:0x000000, white:0xFFFFFF, red:0xFF0000 }}; * var s:String = JSON.stringify(JSONObj); * var o:Object = JSON.parse(s); * trace (JSON.$+" stringified: " + s); * trace (JSON.$+" objectified: "+o); *} *@see JSON *@see cinqetdemi.JSON */ class com.sekati.data.JSON extends CoreObject { /* BACKSLASH should be a simple "\\" but as2api craps out so * we will use the ASCII representation of the backslash. * The side effect is that special characters in JSON Object * strings (tab,return,etc) will also be represented in ASCII. */ private static var BACKSLASH:String = "\u005C"; private static var _instance:JSON; private var at:Number; private var ch:String; private var text:String; private var outer:Boolean; /** * Singleton Private Constructor */ private function JSON() { super( ); at = 0; ch = " "; } /** * Singleton Accessor * @return JSON */ public static function getInstance():JSON { if (!_instance) { _instance = new JSON( ); } return _instance; } /** * Shorthand Singleton accessor getter * @return JSON */ public static function get $():JSON { return JSON.getInstance( ); } /** * Transform an actionscript object to a JSON string * @param arg (Object) The object to jsonize * @return String - The JSON string */ static function stringify(arg:Object):String { var c:Object, i:Number, l:Number, s:String = '', v:Object; switch (typeof arg) { case 'object': if (arg) { if (arg instanceof Array) { for (i = 0; i < arg.length ; ++i) { v = stringify( arg[i] ); if (s) { s += ','; } s += String( v ); } return '[' + s + ']'; } else if (typeof arg.toString != 'undefined') { for (i in arg) { v = arg[i]; if (typeof v != 'undefined' && typeof v != 'function') { v = stringify( v ); if (s) { s += ','; } s += i + ":" + v; } } return '{' + s + '}'; } } return 'null'; case 'number': return isFinite( arg ) ? String( arg ) : 'null'; case 'string': l = arg.length; s = '"'; for (i = 0; i < l ; i += 1) { c = arg.charAt( i ); if (c >= ' ') { var cond1:Boolean = (c == BACKSLASH); if (cond1 || c == '"') { s += BACKSLASH; } s += String( c ); } else { switch (c) { case BACKSLASH + "b": s += BACKSLASH + 'b'; break; case BACKSLASH + "f": s += BACKSLASH + 'f'; break; case BACKSLASH + "n": s += BACKSLASH + 'n'; break; case BACKSLASH + "r": s += BACKSLASH + 'r'; break; case BACKSLASH + "t": s += BACKSLASH + 't'; break; default: c = c.charCodeAt( ); s += BACKSLASH + 'u00' + Math.floor( Number( c ) / 16 ).toString( 16 ) + (Number( c ) % 16).toString( 16 ); } } } return s + '"'; case 'boolean': return String( arg ); default: return 'null'; } } /** * Parse a JSON string and return an ActionScript object * @param text (String) The string to parse * @return Object - The outputted ActionScript object */ public static function parse(text:String):Object { var _instance:JSON = getInstance( ); _instance.at = 0; _instance.ch = " "; _instance.text = text; return _instance.value( ); } private function error(m:Object):Void { } private function next():String { ch = text.charAt( at ); at += 1; return ch; } private function white():Void { while (ch) { if (ch <= " ") { this.next( ); } else if (ch == "/") { switch (this.next( )) { case "/" : while (this.next( ) && ch != BACKSLASH + "n" && ch != "r") { } break; case "*" : this.next( ); while (true) { if (ch) { if (ch == "*") { if (this.next( ) == "/") { next( ); break; } } else { this.next( ); } } else { error( "Unterminated comment" ); } } break; default : this.error( "Syntax error" ); } } else { break; } } } private function str():String { var i:Number, s:String = "", t:Number, u:Number; outer = false; if (ch == '"' || ch == '"') { var outerChar:String = ch; while (this.next( )) { if (ch == outerChar) { this.next( ); return s; } else if (ch == BACKSLASH) { switch (this.next( )) { case "b" : s += "b"; break; case "f" : s += "f"; break; case "n" : s += BACKSLASH + "n"; break; case "r" : s += "r"; break; case "t" : s += BACKSLASH + "t"; break; case "u" : u = 0; for (i = 0; i < 4 ; i += 1) { t = parseInt( this.next( ), 16 ); if (!isFinite( t )) { outer = true; break; } u = u * 16 + t; } if (outer) { outer = false; break; } s += String.fromCharCode( u ); break; default : s += ch; } } else { s += ch; } } } this.error( "Bad string" ); } private function key():String { var s:String = ch; outer = false; var semiColon:Number = text.indexOf( ":", at ); //Use string handling var quoteIndex:Number = text.indexOf( '"', at ); var squoteIndex:Number = text.indexOf( '"', at ); if ((quoteIndex <= semiColon && quoteIndex > -1) || (squoteIndex <= semiColon && squoteIndex > -1)) { s = str( ); white( ); trace( ch ); if (ch == ":") { return s; } else { this.error( "Bad key" ); } } //Use key handling while (this.next( )) { if (ch == ":") { return s; } if (ch <= " ") { // } else { s += ch; } } this.error( "Bad key" ); } private function arr():Array { var a:Array = []; if (ch == "[") { this.next( ); this.white( ); if (ch == "]") { this.next( ); return a; } while (ch) { if (ch == "]") { this.next( ); return a; } a.push( this.value( ) ); this.white( ); if (ch == "]") { this.next( ); return a; } else if (ch != ",") { break; } this.next( ); this.white( ); } } this.error( "Bad array" ); } private function obj():Object { var k:String, o:Object = {}; if (ch == "{") { this.next( ); this.white( ); if (ch == "}") { this.next( ); return o; } while (ch) { if (ch == "}") { this.next( ); return o; } k = this.key( ); if (ch != ":") { break; } this.next( ); o[k] = this.value( ); this.white( ); if (ch == "}") { this.next( ); return o; } else if (ch != ",") { break; } this.next( ); this.white( ); } } this.error( "Bad object" ); } private function num():Number { var n:String = "", v:Number; if (ch == "-") { n = "-"; this.next( ); } while ((ch >= "0" && ch <= "9") || ch == "x" || (ch >= "a" && ch <= "f") || (ch >= "A" && ch <= "F")) { n += ch; this.next( ); } if (ch == ".") { n += "."; this.next( ); while (ch >= "0" && ch <= "9") { n += ch; this.next( ); } } if (ch == "e" || ch == "E") { n += ch; this.next( ); if (ch == "-" || ch == "+") { n += ch; this.next( ); } while (ch >= "0" && ch <= "9") { n += ch; this.next( ); } } v = Number( n ); if (!isFinite( v )) { this.error( "Bad number" ); } return v; } private function word():Boolean { switch (ch) { case "t" : if (this.next( ) == "r" && this.next( ) == "u" && this.next( ) == "e") { this.next( ); return true; } break; case "f" : if (this.next( ) == "a" && this.next( ) == "l" && this.next( ) == "s" && this.next( ) == "e") { this.next( ); return false; } break; case "n" : if (this.next( ) == "u" && this.next( ) == "l" && this.next( ) == "l") { this.next( ); return null; } break; } this.error( "Syntax error" ); } private function value():Object { this.white( ); switch (ch) { case "{" : return this.obj( ); case "[" : return this.arr( ); case '"' : case '"' : return this.str( ); case "-" : return this.num( ); default : return ch >= "0" && ch <= "9" ? this.num( ) : this.word( ); } } }