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: }