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