/**
* 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( );
}
}
}