JSONEncoder.as 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. /*
  2. Copyright (c) 2008, Adobe Systems Incorporated
  3. All rights reserved.
  4. Redistribution and use in source and binary forms, with or without
  5. modification, are permitted provided that the following conditions are
  6. met:
  7. * Redistributions of source code must retain the above copyright notice,
  8. this list of conditions and the following disclaimer.
  9. * Redistributions in binary form must reproduce the above copyright
  10. notice, this list of conditions and the following disclaimer in the
  11. documentation and/or other materials provided with the distribution.
  12. * Neither the name of Adobe Systems Incorporated nor the names of its
  13. contributors may be used to endorse or promote products derived from
  14. this software without specific prior written permission.
  15. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
  16. IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
  17. THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  18. PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  19. CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  20. EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  21. PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  22. PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  23. LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  24. NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  25. SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  26. */
  27. package com.adobe.serialization.json
  28. {
  29. import flash.utils.describeType;
  30. public class JSONEncoder {
  31. /** The string that is going to represent the object we're encoding */
  32. private var jsonString:String;
  33. /**
  34. * Creates a new JSONEncoder.
  35. *
  36. * @param o The object to encode as a JSON string
  37. * @langversion ActionScript 3.0
  38. * @playerversion Flash 9.0
  39. * @tiptext
  40. */
  41. public function JSONEncoder( value:* ) {
  42. jsonString = convertToString( value );
  43. }
  44. /**
  45. * Gets the JSON string from the encoder.
  46. *
  47. * @return The JSON string representation of the object
  48. * that was passed to the constructor
  49. * @langversion ActionScript 3.0
  50. * @playerversion Flash 9.0
  51. * @tiptext
  52. */
  53. public function getString():String {
  54. return jsonString;
  55. }
  56. /**
  57. * Converts a value to it's JSON string equivalent.
  58. *
  59. * @param value The value to convert. Could be any
  60. * type (object, number, array, etc)
  61. */
  62. private function convertToString( value:* ):String {
  63. // determine what value is and convert it based on it's type
  64. if ( value is String ) {
  65. // escape the string so it's formatted correctly
  66. return escapeString( value as String );
  67. } else if ( value is Number ) {
  68. // only encode numbers that finate
  69. return isFinite( value as Number) ? value.toString() : "null";
  70. } else if ( value is Boolean ) {
  71. // convert boolean to string easily
  72. return value ? "true" : "false";
  73. } else if ( value is Array ) {
  74. // call the helper method to convert an array
  75. return arrayToString( value as Array );
  76. } else if ( value is Object && value != null ) {
  77. // call the helper method to convert an object
  78. return objectToString( value );
  79. }
  80. return "null";
  81. }
  82. /**
  83. * Escapes a string according to the JSON specification.
  84. *
  85. * @param str The string to be escaped
  86. * @return The string with escaped special characters
  87. * according to the JSON specification
  88. */
  89. private function escapeString( str:String ):String {
  90. // create a string to store the string's jsonstring value
  91. var s:String = "";
  92. // current character in the string we're processing
  93. var ch:String;
  94. // store the length in a local variable to reduce lookups
  95. var len:Number = str.length;
  96. // loop over all of the characters in the string
  97. for ( var i:int = 0; i < len; i++ ) {
  98. // examine the character to determine if we have to escape it
  99. ch = str.charAt( i );
  100. switch ( ch ) {
  101. case '"': // quotation mark
  102. s += "\\\"";
  103. break;
  104. //case '/': // solidus
  105. // s += "\\/";
  106. // break;
  107. case '\\': // reverse solidus
  108. s += "\\\\";
  109. break;
  110. case '\b': // bell
  111. s += "\\b";
  112. break;
  113. case '\f': // form feed
  114. s += "\\f";
  115. break;
  116. case '\n': // newline
  117. s += "\\n";
  118. break;
  119. case '\r': // carriage return
  120. s += "\\r";
  121. break;
  122. case '\t': // horizontal tab
  123. s += "\\t";
  124. break;
  125. default: // everything else
  126. // check for a control character and escape as unicode
  127. if ( ch < ' ' ) {
  128. // get the hex digit(s) of the character (either 1 or 2 digits)
  129. var hexCode:String = ch.charCodeAt( 0 ).toString( 16 );
  130. // ensure that there are 4 digits by adjusting
  131. // the # of zeros accordingly.
  132. var zeroPad:String = hexCode.length == 2 ? "00" : "000";
  133. // create the unicode escape sequence with 4 hex digits
  134. s += "\\u" + zeroPad + hexCode;
  135. } else {
  136. // no need to do any special encoding, just pass-through
  137. s += ch;
  138. }
  139. } // end switch
  140. } // end for loop
  141. return "\"" + s + "\"";
  142. }
  143. /**
  144. * Converts an array to it's JSON string equivalent
  145. *
  146. * @param a The array to convert
  147. * @return The JSON string representation of <code>a</code>
  148. */
  149. private function arrayToString( a:Array ):String {
  150. // create a string to store the array's jsonstring value
  151. var s:String = "";
  152. // loop over the elements in the array and add their converted
  153. // values to the string
  154. for ( var i:int = 0; i < a.length; i++ ) {
  155. // when the length is 0 we're adding the first element so
  156. // no comma is necessary
  157. if ( s.length > 0 ) {
  158. // we've already added an element, so add the comma separator
  159. s += ","
  160. }
  161. // convert the value to a string
  162. s += convertToString( a[i] );
  163. }
  164. // KNOWN ISSUE: In ActionScript, Arrays can also be associative
  165. // objects and you can put anything in them, ie:
  166. // myArray["foo"] = "bar";
  167. //
  168. // These properties aren't picked up in the for loop above because
  169. // the properties don't correspond to indexes. However, we're
  170. // sort of out luck because the JSON specification doesn't allow
  171. // these types of array properties.
  172. //
  173. // So, if the array was also used as an associative object, there
  174. // may be some values in the array that don't get properly encoded.
  175. //
  176. // A possible solution is to instead encode the Array as an Object
  177. // but then it won't get decoded correctly (and won't be an
  178. // Array instance)
  179. // close the array and return it's string value
  180. return "[" + s + "]";
  181. }
  182. /**
  183. * Converts an object to it's JSON string equivalent
  184. *
  185. * @param o The object to convert
  186. * @return The JSON string representation of <code>o</code>
  187. */
  188. private function objectToString( o:Object ):String
  189. {
  190. // create a string to store the object's jsonstring value
  191. var s:String = "";
  192. // determine if o is a class instance or a plain object
  193. var classInfo:XML = describeType( o );
  194. if ( classInfo.@name.toString() == "Object" )
  195. {
  196. // the value of o[key] in the loop below - store this
  197. // as a variable so we don't have to keep looking up o[key]
  198. // when testing for valid values to convert
  199. var value:Object;
  200. // loop over the keys in the object and add their converted
  201. // values to the string
  202. for ( var key:String in o )
  203. {
  204. // assign value to a variable for quick lookup
  205. value = o[key];
  206. // don't add function's to the JSON string
  207. if ( value is Function )
  208. {
  209. // skip this key and try another
  210. continue;
  211. }
  212. // when the length is 0 we're adding the first item so
  213. // no comma is necessary
  214. if ( s.length > 0 ) {
  215. // we've already added an item, so add the comma separator
  216. s += ","
  217. }
  218. s += escapeString( key ) + ":" + convertToString( value );
  219. }
  220. }
  221. else // o is a class instance
  222. {
  223. // Loop over all of the variables and accessors in the class and
  224. // serialize them along with their values.
  225. for each ( var v:XML in classInfo..*.(
  226. name() == "variable"
  227. ||
  228. (
  229. name() == "accessor"
  230. // Issue #116 - Make sure accessors are readable
  231. && attribute( "access" ).charAt( 0 ) == "r" )
  232. ) )
  233. {
  234. // Issue #110 - If [Transient] metadata exists, then we should skip
  235. if ( v.metadata && v.metadata.( @name == "Transient" ).length() > 0 )
  236. {
  237. continue;
  238. }
  239. // When the length is 0 we're adding the first item so
  240. // no comma is necessary
  241. if ( s.length > 0 ) {
  242. // We've already added an item, so add the comma separator
  243. s += ","
  244. }
  245. s += escapeString( v.@name.toString() ) + ":"
  246. + convertToString( o[ v.@name ] );
  247. }
  248. }
  249. return "{" + s + "}";
  250. }
  251. }
  252. }