1  /**
     2   * com.sekati.data.JSON
     3   * @version 1.0.3
     4   * @author jason m horwitz | sekati.com
     5   * Copyright (C) 2007  jason m horwitz, Sekat LLC. All Rights Reserved.
     6   * Released under the MIT License: http://www.opensource.org/licenses/mit-license.php
     7   * 
     8   * Adapted from cinqetdemi.JSON for dependencies only.
     9   */
    10  
    11  import com.sekati.core.CoreObject;
    12  /**
    13   * JSON (JavaScript Object Notation) Serializing/Deserializing Parser.
    14   * 
    15   * {@code Usage:
    16   * var JSONObj:Object = {unlucky:13, lucky:7, household:{ people:['Jason','Therese'], pets:['Finn','Van'] }, colors:{ black:0x000000, white:0xFFFFFF, red:0xFF0000 }};
    17   * var s:String = JSON.stringify(JSONObj);
    18   * var o:Object = JSON.parse(s);
    19   * trace (JSON.$+" stringified: " + s);
    20   * trace (JSON.$+" objectified: "+o);
    21   *}
    22   *@see <a href="http://www.json.org/">JSON</a>
    23   *@see <a href="http://www.5etdemi.com/blog/archives/2006/05/cinqetdemijson-a-better-json-parser-for-as2-suited-for-config-files/">cinqetdemi.JSON</a>
    24   */
    25  class com.sekati.data.JSON extends CoreObject {
    26  	/* BACKSLASH should be a simple "\\" but as2api craps out so 
    27  	 * we will use the ASCII representation of the backslash.
    28  	 * The side effect is that special characters in JSON Object
    29  	 * strings (tab,return,etc) will also be represented in ASCII.
    30  	 */
    31  	private static var BACKSLASH:String = "u005C";
    32  	private static var _instance:JSON;
    33  	private var at:Number;
    34  	private var ch:String;
    35  	private var text:String;
    36  	private var outer:Boolean;
    37  	/**
    38  	 * Singleton Private Constructor
    39  	 */
    40  	private function JSON() {
    41  		super( );
    42  		at = 0;
    43  		ch = " ";
    44  	}
    45  	/**
    46  	 * Singleton Accessor
    47  	 * @return JSON
    48  	 */
    49  	public static function getInstance():JSON {
    50  		if (!_instance) {
    51  			_instance = new JSON( );
    52  		}
    53  		return _instance;
    54  	}
    55  	/**
    56  	 * Shorthand Singleton accessor getter
    57  	 * @return JSON
    58  	 */
    59  	public static function get $():JSON {
    60  		return JSON.getInstance( );
    61  	}
    62  	/**
    63  	 * Transform an actionscript object to a JSON string
    64  	 * @param arg (Object) The object to jsonize
    65  	 * @return String - The JSON string
    66  	 */
    67  	static function stringify(arg:Object):String {
    68  
    69  		var c:Object, i:Number, l:Number, s:String = "", v:Object;
    70  
    71  		switch (typeof arg) {
    72  			case "object":
    73  				if (arg) {
    74  					if (arg instanceof Array) {
    75  						for (i = 0; i < arg.length ; ++i) {
    76  							v = stringify( arg[i] );
    77  							if (s) {
    78  								s += ",";
    79  							}
    80  							s += String( v );
    81  						}
    82  						return "[" + s + "]";
    83  					} else if (typeof arg.toString != "undefined") {
    84  						for (i in arg) {
    85  							v = arg[i];
    86  							if (typeof v != "undefined" && typeof v != "function") {
    87  								v = stringify( v );
    88  								if (s) {
    89  									s += ",";
    90  								}
    91  								s += i + ":" + v;
    92  							}
    93  						}
    94  						return "{" + s + "}";
    95  					}
    96  				}
    97  				return "null";
    98  			case "number":
    99  				return isFinite( arg ) ? String( arg ) : "null";
   100  			case "string":
   101  				l = arg.length;
   102  				s = "\"";
   103  				for (i = 0; i < l ; i += 1) {
   104  					c = arg.charAt( i );
   105  					if (c >= " ") {
   106  						var cond1:Boolean = (c == BACKSLASH); 
   107  						if (cond1 || c == "\"") {
   108  							s += BACKSLASH;
   109  						}
   110  						s += String( c );
   111  					} else {
   112  						switch (c) {
   113  							case BACKSLASH + "b":
   114  								s += BACKSLASH + "b";
   115                             
   116  								break;
   117  							case BACKSLASH + "f":
   118                              
   119  								s += BACKSLASH + "f";
   120  								break;
   121  							case BACKSLASH + "n":
   122                              
   123  								s += BACKSLASH + "n";
   124  								break;
   125  							case BACKSLASH + "r":
   126                              
   127  								s += BACKSLASH + "r";
   128  								break;
   129  							case BACKSLASH + "t":
   130                             
   131  								s += BACKSLASH + "t";
   132  								break;
   133  							default:
   134  								c = c.charCodeAt( );
   135                             
   136  								s += BACKSLASH + "u00" + Math.floor( Number( c ) / 16 ).toString( 16 ) + (Number( c ) % 16).toString( 16 );
   137  						}
   138  					}
   139  				}
   140  				return s + "\"";
   141  			case "boolean":
   142  				return String( arg );
   143  			default:
   144  				return "null";
   145  		}
   146  	}	 
   147  	/**
   148  	 * Parse a JSON string and return an ActionScript object
   149  	 * @param text (String) The string to parse
   150  	 * @return Object - The outputted ActionScript object
   151  	 */
   152  	public static function parse(text:String):Object {
   153  
   154  		var _instance:JSON = getInstance( );
   155  		_instance.at = 0;
   156  		_instance.ch = " ";
   157  		_instance.text = text;
   158  
   159  		return _instance.value( );
   160  	}
   161  	private function error(m:Object):Void {
   162  	}
   163  	private function next():String {
   164  		ch = text.charAt( at );
   165  		at += 1;
   166  		return ch;
   167  	}
   168  	private function white():Void {
   169  		while (ch) {
   170  			if (ch <= " ") {
   171  				this.next( );
   172  			} else if (ch == "/") {
   173  				switch (this.next( )) {
   174  					case "/" :
   175  						while (this.next( ) && ch != BACKSLASH + "n" && ch != "r") {
   176  						}
   177  						break;
   178  					case "*" :
   179  						this.next( );
   180  						while (true) {
   181  							if (ch) {
   182  								if (ch == "*") {
   183  									if (this.next( ) == "/") {
   184  										next( );
   185  										break;
   186  									}
   187  								} else {
   188  									this.next( );
   189  								}
   190  							} else {
   191  								error( "Unterminated comment" );
   192  							}
   193  						}
   194  						break;
   195  					default :
   196  						this.error( "Syntax error" );
   197  				}
   198  			} else {
   199  				break;
   200  			}
   201  		}
   202  	}
   203  	private function str():String {
   204  		var i:Number, s:String = "", t:Number, u:Number;
   205  		outer = false;
   206  
   207  		if (ch == "\"" || ch == "\"") {
   208  			var outerChar:String = ch;
   209  			while (this.next( )) {
   210  				if (ch == outerChar) {
   211  					this.next( );
   212  					return s;
   213  				} else if (ch == BACKSLASH) {
   214  					switch (this.next( )) {
   215  						case "b" :
   216  							s += "b";
   217  							break;
   218  						case "f" :
   219  							s += "f";
   220  							break;
   221  						case "n" :
   222  							s += BACKSLASH + "n";
   223  							break;
   224  						case "r" :
   225  							s += "r";
   226  							break;
   227  						case "t" :
   228  							s += BACKSLASH + "t";
   229  							break;
   230  						case "u" :
   231  							u = 0;
   232  							for (i = 0; i < 4 ; i += 1) {
   233  								t = parseInt( this.next( ), 16 );
   234  								if (!isFinite( t )) {
   235  									outer = true;
   236  									break;
   237  								}
   238  								u = u * 16 + t;
   239  							}
   240  							if (outer) {
   241  								outer = false;
   242  								break;
   243  							}
   244  							s += String.fromCharCode( u );
   245  							break;
   246  						default :
   247  							s += ch;
   248  					}
   249  				} else {
   250  					s += ch;
   251  				}
   252  			}
   253  		}
   254  		this.error( "Bad string" );
   255  	}
   256  	private function key():String {
   257  		var s:String = ch;
   258  		outer = false;
   259  
   260  		var semiColon:Number = text.indexOf( ":", at );
   261  
   262  		//Use string handling
   263  		var quoteIndex:Number = text.indexOf( "\"", at );
   264  		var squoteIndex:Number = text.indexOf( "\"", at );
   265  		if ((quoteIndex <= semiColon && quoteIndex > -1) || (squoteIndex <= semiColon && squoteIndex > -1)) {
   266  			s = str( );
   267  			white( );
   268  			trace( ch );
   269  			if (ch == ":") {
   270  				return s;
   271  			} else {
   272  				this.error( "Bad key" );
   273  			}
   274  		}
   275  		//Use key handling   
   276  		while (this.next( )) {
   277  			if (ch == ":") {
   278  				return s;
   279  			}
   280  			if (ch <= " ") {
   281  				//
   282  			} else {
   283  				s += ch;
   284  			}
   285  		}
   286  		this.error( "Bad key" );
   287  	}
   288  	private function arr():Array {
   289  		var a:Array = [];
   290  
   291  		if (ch == "[") {
   292  			this.next( );
   293  			this.white( );
   294  			if (ch == "]") {
   295  				this.next( );
   296  				return a;
   297  			}
   298  			while (ch) {
   299  				if (ch == "]") {
   300  					this.next( );
   301  					return a;
   302  				}
   303  				a.push( this.value( ) );
   304  				this.white( );
   305  				if (ch == "]") {
   306  					this.next( );
   307  					return a;
   308  				} else if (ch != ",") {
   309  					break;
   310  				}
   311  				this.next( );
   312  				this.white( );
   313  			}
   314  		}
   315  		this.error( "Bad array" );
   316  	}
   317  	private function obj():Object {
   318  		var k:String, o:Object = {};
   319  
   320  		if (ch == "{") {
   321  			this.next( );
   322  			this.white( );
   323  			if (ch == "}") {
   324  				this.next( );
   325  				return o;
   326  			}
   327  			while (ch) {
   328  				if (ch == "}") {
   329  					this.next( );
   330  					return o;
   331  				}
   332  				k = this.key( );
   333  				if (ch != ":") {
   334  					break;
   335  				}
   336  				this.next( );
   337  				o[k] = this.value( );
   338  				this.white( );
   339  				if (ch == "}") {
   340  					this.next( );
   341  					return o;
   342  				} else if (ch != ",") {
   343  					break;
   344  				}
   345  				this.next( );
   346  				this.white( );
   347  			}
   348  		}
   349  		this.error( "Bad object" );
   350  	}
   351  	private function num():Number {
   352  		var n:String = "", v:Number;
   353  
   354  		if (ch == "-") {
   355  			n = "-";
   356  			this.next( );
   357  		}
   358  		while ((ch >= "0" && ch <= "9") || ch == "x" || (ch >= "a" && ch <= "f") || (ch >= "A" && ch <= "F")) {
   359  			n += ch;
   360  			this.next( );
   361  		}
   362  		if (ch == ".") {
   363  			n += ".";
   364  			this.next( );
   365  			while (ch >= "0" && ch <= "9") {
   366  				n += ch;
   367  				this.next( );
   368  			}
   369  		}
   370  		if (ch == "e" || ch == "E") {
   371  			n += ch;
   372  			this.next( );
   373  			if (ch == "-" || ch == "+") {
   374  				n += ch;
   375  				this.next( );
   376  			}
   377  			while (ch >= "0" && ch <= "9") {
   378  				n += ch;
   379  				this.next( );
   380  			}
   381  		}
   382  		v = Number( n );
   383  		if (!isFinite( v )) {
   384  			this.error( "Bad number" );
   385  		}
   386  		return v;
   387  	}
   388  	private function word():Boolean {
   389  		switch (ch) {
   390  			case "t" :
   391  				if (this.next( ) == "r" && this.next( ) == "u" && this.next( ) == "e") {
   392  					this.next( );
   393  					return true;
   394  				}
   395  				break;
   396  			case "f" :
   397  				if (this.next( ) == "a" && this.next( ) == "l" && this.next( ) == "s" && this.next( ) == "e") {
   398  					this.next( );
   399  					return false;
   400  				}
   401  				break;
   402  			case "n" :
   403  				if (this.next( ) == "u" && this.next( ) == "l" && this.next( ) == "l") {
   404  					this.next( );
   405  					return null;
   406  				}
   407  				break;
   408  		}
   409  		this.error( "Syntax error" );
   410  	}
   411  	private function value():Object {
   412  		this.white( );
   413  		switch (ch) {
   414  			case "{" :
   415  				return this.obj( );
   416  			case "[" :
   417  				return this.arr( );
   418  			case "\"" :
   419  			case "\"" :
   420  				return this.str( );
   421  			case "-" :
   422  				return this.num( );
   423  			default :
   424  				return ch >= "0" && ch <= "9" ? this.num( ) : this.word( );
   425  		}
   426  	}
   427  }