mas01mj@732: /*
mas01mj@732: Copyright (c) 2008, Adobe Systems Incorporated
mas01mj@732: All rights reserved.
mas01mj@732:
mas01mj@732: Redistribution and use in source and binary forms, with or without
mas01mj@732: modification, are permitted provided that the following conditions are
mas01mj@732: met:
mas01mj@732:
mas01mj@732: * Redistributions of source code must retain the above copyright notice,
mas01mj@732: this list of conditions and the following disclaimer.
mas01mj@732:
mas01mj@732: * Redistributions in binary form must reproduce the above copyright
mas01mj@732: notice, this list of conditions and the following disclaimer in the
mas01mj@732: documentation and/or other materials provided with the distribution.
mas01mj@732:
mas01mj@732: * Neither the name of Adobe Systems Incorporated nor the names of its
mas01mj@732: contributors may be used to endorse or promote products derived from
mas01mj@732: this software without specific prior written permission.
mas01mj@732:
mas01mj@732: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
mas01mj@732: IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
mas01mj@732: THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
mas01mj@732: PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
mas01mj@732: CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
mas01mj@732: EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
mas01mj@732: PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
mas01mj@732: PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
mas01mj@732: LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
mas01mj@732: NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
mas01mj@732: SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
mas01mj@732: */
mas01mj@732:
mas01mj@732: package com.adobe.serialization.json
mas01mj@732: {
mas01mj@732:
mas01mj@732: import flash.utils.describeType;
mas01mj@732:
mas01mj@732: public class JSONEncoder {
mas01mj@732:
mas01mj@732: /** The string that is going to represent the object we're encoding */
mas01mj@732: private var jsonString:String;
mas01mj@732:
mas01mj@732: /**
mas01mj@732: * Creates a new JSONEncoder.
mas01mj@732: *
mas01mj@732: * @param o The object to encode as a JSON string
mas01mj@732: * @langversion ActionScript 3.0
mas01mj@732: * @playerversion Flash 9.0
mas01mj@732: * @tiptext
mas01mj@732: */
mas01mj@732: public function JSONEncoder( value:* ) {
mas01mj@732: jsonString = convertToString( value );
mas01mj@732:
mas01mj@732: }
mas01mj@732:
mas01mj@732: /**
mas01mj@732: * Gets the JSON string from the encoder.
mas01mj@732: *
mas01mj@732: * @return The JSON string representation of the object
mas01mj@732: * that was passed to the constructor
mas01mj@732: * @langversion ActionScript 3.0
mas01mj@732: * @playerversion Flash 9.0
mas01mj@732: * @tiptext
mas01mj@732: */
mas01mj@732: public function getString():String {
mas01mj@732: return jsonString;
mas01mj@732: }
mas01mj@732:
mas01mj@732: /**
mas01mj@732: * Converts a value to it's JSON string equivalent.
mas01mj@732: *
mas01mj@732: * @param value The value to convert. Could be any
mas01mj@732: * type (object, number, array, etc)
mas01mj@732: */
mas01mj@732: private function convertToString( value:* ):String {
mas01mj@732:
mas01mj@732: // determine what value is and convert it based on it's type
mas01mj@732: if ( value is String ) {
mas01mj@732:
mas01mj@732: // escape the string so it's formatted correctly
mas01mj@732: return escapeString( value as String );
mas01mj@732:
mas01mj@732: } else if ( value is Number ) {
mas01mj@732:
mas01mj@732: // only encode numbers that finate
mas01mj@732: return isFinite( value as Number) ? value.toString() : "null";
mas01mj@732:
mas01mj@732: } else if ( value is Boolean ) {
mas01mj@732:
mas01mj@732: // convert boolean to string easily
mas01mj@732: return value ? "true" : "false";
mas01mj@732:
mas01mj@732: } else if ( value is Array ) {
mas01mj@732:
mas01mj@732: // call the helper method to convert an array
mas01mj@732: return arrayToString( value as Array );
mas01mj@732:
mas01mj@732: } else if ( value is Object && value != null ) {
mas01mj@732:
mas01mj@732: // call the helper method to convert an object
mas01mj@732: return objectToString( value );
mas01mj@732: }
mas01mj@732: return "null";
mas01mj@732: }
mas01mj@732:
mas01mj@732: /**
mas01mj@732: * Escapes a string accoding to the JSON specification.
mas01mj@732: *
mas01mj@732: * @param str The string to be escaped
mas01mj@732: * @return The string with escaped special characters
mas01mj@732: * according to the JSON specification
mas01mj@732: */
mas01mj@732: private function escapeString( str:String ):String {
mas01mj@732: // create a string to store the string's jsonstring value
mas01mj@732: var s:String = "";
mas01mj@732: // current character in the string we're processing
mas01mj@732: var ch:String;
mas01mj@732: // store the length in a local variable to reduce lookups
mas01mj@732: var len:Number = str.length;
mas01mj@732:
mas01mj@732: // loop over all of the characters in the string
mas01mj@732: for ( var i:int = 0; i < len; i++ ) {
mas01mj@732:
mas01mj@732: // examine the character to determine if we have to escape it
mas01mj@732: ch = str.charAt( i );
mas01mj@732: switch ( ch ) {
mas01mj@732:
mas01mj@732: case '"': // quotation mark
mas01mj@732: s += "\\\"";
mas01mj@732: break;
mas01mj@732:
mas01mj@732: //case '/': // solidus
mas01mj@732: // s += "\\/";
mas01mj@732: // break;
mas01mj@732:
mas01mj@732: case '\\': // reverse solidus
mas01mj@732: s += "\\\\";
mas01mj@732: break;
mas01mj@732:
mas01mj@732: case '\b': // bell
mas01mj@732: s += "\\b";
mas01mj@732: break;
mas01mj@732:
mas01mj@732: case '\f': // form feed
mas01mj@732: s += "\\f";
mas01mj@732: break;
mas01mj@732:
mas01mj@732: case '\n': // newline
mas01mj@732: s += "\\n";
mas01mj@732: break;
mas01mj@732:
mas01mj@732: case '\r': // carriage return
mas01mj@732: s += "\\r";
mas01mj@732: break;
mas01mj@732:
mas01mj@732: case '\t': // horizontal tab
mas01mj@732: s += "\\t";
mas01mj@732: break;
mas01mj@732:
mas01mj@732: default: // everything else
mas01mj@732:
mas01mj@732: // check for a control character and escape as unicode
mas01mj@732: if ( ch < ' ' ) {
mas01mj@732: // get the hex digit(s) of the character (either 1 or 2 digits)
mas01mj@732: var hexCode:String = ch.charCodeAt( 0 ).toString( 16 );
mas01mj@732:
mas01mj@732: // ensure that there are 4 digits by adjusting
mas01mj@732: // the # of zeros accordingly.
mas01mj@732: var zeroPad:String = hexCode.length == 2 ? "00" : "000";
mas01mj@732:
mas01mj@732: // create the unicode escape sequence with 4 hex digits
mas01mj@732: s += "\\u" + zeroPad + hexCode;
mas01mj@732: } else {
mas01mj@732:
mas01mj@732: // no need to do any special encoding, just pass-through
mas01mj@732: s += ch;
mas01mj@732:
mas01mj@732: }
mas01mj@732: } // end switch
mas01mj@732:
mas01mj@732: } // end for loop
mas01mj@732:
mas01mj@732: return "\"" + s + "\"";
mas01mj@732: }
mas01mj@732:
mas01mj@732: /**
mas01mj@732: * Converts an array to it's JSON string equivalent
mas01mj@732: *
mas01mj@732: * @param a The array to convert
mas01mj@732: * @return The JSON string representation of a
mas01mj@732: */
mas01mj@732: private function arrayToString( a:Array ):String {
mas01mj@732: // create a string to store the array's jsonstring value
mas01mj@732: var s:String = "";
mas01mj@732:
mas01mj@732: // loop over the elements in the array and add their converted
mas01mj@732: // values to the string
mas01mj@732: for ( var i:int = 0; i < a.length; i++ ) {
mas01mj@732: // when the length is 0 we're adding the first element so
mas01mj@732: // no comma is necessary
mas01mj@732: if ( s.length > 0 ) {
mas01mj@732: // we've already added an element, so add the comma separator
mas01mj@732: s += ","
mas01mj@732: }
mas01mj@732:
mas01mj@732: // convert the value to a string
mas01mj@732: s += convertToString( a[i] );
mas01mj@732: }
mas01mj@732:
mas01mj@732: // KNOWN ISSUE: In ActionScript, Arrays can also be associative
mas01mj@732: // objects and you can put anything in them, ie:
mas01mj@732: // myArray["foo"] = "bar";
mas01mj@732: //
mas01mj@732: // These properties aren't picked up in the for loop above because
mas01mj@732: // the properties don't correspond to indexes. However, we're
mas01mj@732: // sort of out luck because the JSON specification doesn't allow
mas01mj@732: // these types of array properties.
mas01mj@732: //
mas01mj@732: // So, if the array was also used as an associative object, there
mas01mj@732: // may be some values in the array that don't get properly encoded.
mas01mj@732: //
mas01mj@732: // A possible solution is to instead encode the Array as an Object
mas01mj@732: // but then it won't get decoded correctly (and won't be an
mas01mj@732: // Array instance)
mas01mj@732:
mas01mj@732: // close the array and return it's string value
mas01mj@732: return "[" + s + "]";
mas01mj@732: }
mas01mj@732:
mas01mj@732: /**
mas01mj@732: * Converts an object to it's JSON string equivalent
mas01mj@732: *
mas01mj@732: * @param o The object to convert
mas01mj@732: * @return The JSON string representation of o
mas01mj@732: */
mas01mj@732: private function objectToString( o:Object ):String
mas01mj@732: {
mas01mj@732: // create a string to store the object's jsonstring value
mas01mj@732: var s:String = "";
mas01mj@732:
mas01mj@732: // determine if o is a class instance or a plain object
mas01mj@732: var classInfo:XML = describeType( o );
mas01mj@732: if ( classInfo.@name.toString() == "Object" )
mas01mj@732: {
mas01mj@732: // the value of o[key] in the loop below - store this
mas01mj@732: // as a variable so we don't have to keep looking up o[key]
mas01mj@732: // when testing for valid values to convert
mas01mj@732: var value:Object;
mas01mj@732:
mas01mj@732: // loop over the keys in the object and add their converted
mas01mj@732: // values to the string
mas01mj@732: for ( var key:String in o )
mas01mj@732: {
mas01mj@732: // assign value to a variable for quick lookup
mas01mj@732: value = o[key];
mas01mj@732:
mas01mj@732: // don't add function's to the JSON string
mas01mj@732: if ( value is Function )
mas01mj@732: {
mas01mj@732: // skip this key and try another
mas01mj@732: continue;
mas01mj@732: }
mas01mj@732:
mas01mj@732: // when the length is 0 we're adding the first item so
mas01mj@732: // no comma is necessary
mas01mj@732: if ( s.length > 0 ) {
mas01mj@732: // we've already added an item, so add the comma separator
mas01mj@732: s += ","
mas01mj@732: }
mas01mj@732:
mas01mj@732: s += escapeString( key ) + ":" + convertToString( value );
mas01mj@732: }
mas01mj@732: }
mas01mj@732: else // o is a class instance
mas01mj@732: {
mas01mj@732: // Loop over all of the variables and accessors in the class and
mas01mj@732: // serialize them along with their values.
mas01mj@732: for each ( var v:XML in classInfo..*.(
mas01mj@732: name() == "variable"
mas01mj@732: ||
mas01mj@732: (
mas01mj@732: name() == "accessor"
mas01mj@732: // Issue #116 - Make sure accessors are readable
mas01mj@732: && (""+attribute( "access" )).charAt( 0 ) == "r" )
mas01mj@732: ) )
mas01mj@732: {
mas01mj@732: // Issue #110 - If [Transient] metadata exists, then we should skip
mas01mj@732: if ( v.metadata && v.metadata.( @name == "Transient" ).length() > 0 )
mas01mj@732: {
mas01mj@732: continue;
mas01mj@732: }
mas01mj@732:
mas01mj@732: // When the length is 0 we're adding the first item so
mas01mj@732: // no comma is necessary
mas01mj@732: if ( s.length > 0 ) {
mas01mj@732: // We've already added an item, so add the comma separator
mas01mj@732: s += ","
mas01mj@732: }
mas01mj@732:
mas01mj@732: s += escapeString( v.@name.toString() ) + ":"
mas01mj@732: + convertToString( o[ v.@name ] );
mas01mj@732: }
mas01mj@732:
mas01mj@732: }
mas01mj@732:
mas01mj@732: return "{" + s + "}";
mas01mj@732: }
mas01mj@732:
mas01mj@732:
mas01mj@732: }
mas01mj@732:
mas01mj@732: }