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