root / Assets / Plugins / UnityHTTP / external / JSON.cs @ 9:37719e88568d
History | View | Annotate | Download (12.2 kB)
1 | using System; |
---|---|
2 | using System.Collections; |
3 | using System.Globalization; |
4 | using System.Text; |
5 | |
6 | /* Taken from http://techblog.procurios.nl/k/618/news/view/14605/14863/How-do-I-write-my-own-parser-for-JSON.html |
7 | * MIT Licensed: http://www.opensource.org/licenses/mit-license.php |
8 | */ |
9 | |
10 | /// <summary> |
11 | /// This class encodes and decodes JSON strings. |
12 | /// Spec. details, see http://www.json.org/ |
13 | /// |
14 | /// JSON uses Arrays and Objects. These correspond here to the datatypes ArrayList and Hashtable. |
15 | /// All numbers are parsed to floats or ints. |
16 | /// </summary> |
17 | public class JSON |
18 | { |
19 | public const int TOKEN_NONE = 0; |
20 | public const int TOKEN_CURLY_OPEN = 1; |
21 | public const int TOKEN_CURLY_CLOSE = 2; |
22 | public const int TOKEN_SQUARED_OPEN = 3; |
23 | public const int TOKEN_SQUARED_CLOSE = 4; |
24 | public const int TOKEN_COLON = 5; |
25 | public const int TOKEN_COMMA = 6; |
26 | public const int TOKEN_STRING = 7; |
27 | public const int TOKEN_NUMBER = 8; |
28 | public const int TOKEN_TRUE = 9; |
29 | public const int TOKEN_FALSE = 10; |
30 | public const int TOKEN_NULL = 11; |
31 | |
32 | private const int BUILDER_CAPACITY = 2000; |
33 | /// <summary> |
34 | /// Parses the string json into a value |
35 | /// </summary> |
36 | /// <param name="json">A JSON byte array.</param> |
37 | /// <returns>An ArrayList, a Hashtable, a double, a string, null, true, or false</returns> |
38 | public static object JsonDecode (byte[] json) { |
39 | return JsonDecode(System.Text.ASCIIEncoding.ASCII.GetString(json)); |
40 | } |
41 | |
42 | /// <summary> |
43 | /// Parses the string json into a value |
44 | /// </summary> |
45 | /// <param name="json">A JSON string.</param> |
46 | /// <returns>An ArrayList, a Hashtable, a double, a string, null, true, or false</returns> |
47 | public static object JsonDecode (string json) |
48 | { |
49 | bool success = true; |
50 | |
51 | return JsonDecode (json, ref success); |
52 | } |
53 | |
54 | /// <summary> |
55 | /// Parses the string json into a value; and fills 'success' with the successfullness of the parse. |
56 | /// </summary> |
57 | /// <param name="json">A JSON string.</param> |
58 | /// <param name="success">Successful parse?</param> |
59 | /// <returns>An ArrayList, a Hashtable, a double, a string, null, true, or false</returns> |
60 | public static object JsonDecode (string json, ref bool success) |
61 | { |
62 | success = true; |
63 | if (json != null) { |
64 | char[] charArray = json.ToCharArray (); |
65 | int index = 0; |
66 | object value = ParseValue (charArray, ref index, ref success); |
67 | return value; |
68 | } else { |
69 | return null; |
70 | } |
71 | } |
72 | |
73 | /// <summary> |
74 | /// Converts a Hashtable / ArrayList object into a JSON string |
75 | /// </summary> |
76 | /// <param name="json">A Hashtable / ArrayList</param> |
77 | /// <returns>A JSON encoded string, or null if object 'json' is not serializable</returns> |
78 | public static string JsonEncode (object json) |
79 | { |
80 | StringBuilder builder = new StringBuilder (BUILDER_CAPACITY); |
81 | bool success = SerializeValue (json, builder); |
82 | return (success ? builder.ToString () : null); |
83 | } |
84 | |
85 | protected static Hashtable ParseObject (char[] json, ref int index, ref bool success) |
86 | { |
87 | Hashtable table = new Hashtable (); |
88 | int token; |
89 | |
90 | // { |
91 | NextToken (json, ref index); |
92 | |
93 | bool done = false; |
94 | while (!done) { |
95 | token = LookAhead (json, index); |
96 | if (token == JSON.TOKEN_NONE) { |
97 | success = false; |
98 | return null; |
99 | } else if (token == JSON.TOKEN_COMMA) { |
100 | NextToken (json, ref index); |
101 | } else if (token == JSON.TOKEN_CURLY_CLOSE) { |
102 | NextToken (json, ref index); |
103 | return table; |
104 | } else { |
105 | |
106 | // name |
107 | string name = ParseString (json, ref index, ref success); |
108 | if (!success) { |
109 | success = false; |
110 | return null; |
111 | } |
112 | |
113 | // : |
114 | token = NextToken (json, ref index); |
115 | if (token != JSON.TOKEN_COLON) { |
116 | success = false; |
117 | return null; |
118 | } |
119 | |
120 | // value |
121 | object value = ParseValue (json, ref index, ref success); |
122 | if (!success) { |
123 | success = false; |
124 | return null; |
125 | } |
126 | |
127 | table[name] = value; |
128 | } |
129 | } |
130 | |
131 | return table; |
132 | } |
133 | |
134 | protected static ArrayList ParseArray (char[] json, ref int index, ref bool success) |
135 | { |
136 | ArrayList array = new ArrayList (); |
137 | |
138 | // [ |
139 | NextToken (json, ref index); |
140 | |
141 | bool done = false; |
142 | while (!done) { |
143 | int token = LookAhead (json, index); |
144 | if (token == JSON.TOKEN_NONE) { |
145 | success = false; |
146 | return null; |
147 | } else if (token == JSON.TOKEN_COMMA) { |
148 | NextToken (json, ref index); |
149 | } else if (token == JSON.TOKEN_SQUARED_CLOSE) { |
150 | NextToken (json, ref index); |
151 | break; |
152 | } else { |
153 | object value = ParseValue (json, ref index, ref success); |
154 | if (!success) { |
155 | return null; |
156 | } |
157 | |
158 | array.Add (value); |
159 | } |
160 | } |
161 | |
162 | return array; |
163 | } |
164 | |
165 | protected static object ParseValue (char[] json, ref int index, ref bool success) |
166 | { |
167 | switch (LookAhead (json, index)) { |
168 | case JSON.TOKEN_STRING: |
169 | return ParseString (json, ref index, ref success); |
170 | case JSON.TOKEN_NUMBER: |
171 | return ParseNumber (json, ref index, ref success); |
172 | case JSON.TOKEN_CURLY_OPEN: |
173 | return ParseObject (json, ref index, ref success); |
174 | case JSON.TOKEN_SQUARED_OPEN: |
175 | return ParseArray (json, ref index, ref success); |
176 | case JSON.TOKEN_TRUE: |
177 | NextToken (json, ref index); |
178 | return true; |
179 | case JSON.TOKEN_FALSE: |
180 | NextToken (json, ref index); |
181 | return false; |
182 | case JSON.TOKEN_NULL: |
183 | NextToken (json, ref index); |
184 | return null; |
185 | case JSON.TOKEN_NONE: |
186 | break; |
187 | } |
188 | |
189 | success = false; |
190 | return null; |
191 | } |
192 | |
193 | protected static string ParseString (char[] json, ref int index, ref bool success) |
194 | { |
195 | StringBuilder s = new StringBuilder (BUILDER_CAPACITY); |
196 | char c; |
197 | |
198 | EatWhitespace (json, ref index); |
199 | |
200 | // " |
201 | c = json[index++]; |
202 | |
203 | bool complete = false; |
204 | while (!complete) { |
205 | |
206 | if (index == json.Length) { |
207 | break; |
208 | } |
209 | |
210 | c = json[index++]; |
211 | if (c == '"') { |
212 | complete = true; |
213 | break; |
214 | } else if (c == '\\') { |
215 | |
216 | if (index == json.Length) { |
217 | break; |
218 | } |
219 | c = json[index++]; |
220 | if (c == '"') { |
221 | s.Append ('"'); |
222 | } else if (c == '\\') { |
223 | s.Append ('\\'); |
224 | } else if (c == '/') { |
225 | s.Append ('/'); |
226 | } else if (c == 'b') { |
227 | s.Append ('\b'); |
228 | } else if (c == 'f') { |
229 | s.Append ('\f'); |
230 | } else if (c == 'n') { |
231 | s.Append ('\n'); |
232 | } else if (c == 'r') { |
233 | s.Append ('\r'); |
234 | } else if (c == 't') { |
235 | s.Append ('\t'); |
236 | } else if (c == 'u') { |
237 | int remainingLength = json.Length - index; |
238 | if (remainingLength >= 4) { |
239 | // parse the 32 bit hex into an integer codepoint |
240 | uint codePoint; |
241 | if (!(success = UInt32.TryParse (new string (json, index, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out codePoint))) { |
242 | return ""; |
243 | } |
244 | // convert the integer codepoint to a unicode char and add to string |
245 | s.Append (Char.ConvertFromUtf32 ((int)codePoint)); |
246 | // skip 4 chars |
247 | index += 4; |
248 | } else { |
249 | break; |
250 | } |
251 | } |
252 | |
253 | } else { |
254 | s.Append (c); |
255 | } |
256 | |
257 | } |
258 | |
259 | if (!complete) { |
260 | success = false; |
261 | return null; |
262 | } |
263 | |
264 | return s.ToString (); |
265 | } |
266 | |
267 | protected static object ParseNumber (char[] json, ref int index, ref bool success) |
268 | { |
269 | EatWhitespace (json, ref index); |
270 | |
271 | int lastIndex = GetLastIndexOfNumber (json, index); |
272 | int charLength = (lastIndex - index) + 1; |
273 | |
274 | var token = new string (json, index, charLength); |
275 | index = lastIndex + 1; |
276 | if ( token.Contains( "." ) ) |
277 | { |
278 | float number; |
279 | success = float.TryParse (token, NumberStyles.Any, CultureInfo.InvariantCulture, out number); |
280 | return number; |
281 | } |
282 | else |
283 | { |
284 | int number; |
285 | success = int.TryParse(token, out number); |
286 | return number; |
287 | } |
288 | } |
289 | |
290 | protected static int GetLastIndexOfNumber (char[] json, int index) |
291 | { |
292 | int lastIndex; |
293 | |
294 | for (lastIndex = index; lastIndex < json.Length; lastIndex++) { |
295 | if ("0123456789+-.eE".IndexOf (json[lastIndex]) == -1) { |
296 | break; |
297 | } |
298 | } |
299 | return lastIndex - 1; |
300 | } |
301 | |
302 | protected static void EatWhitespace (char[] json, ref int index) |
303 | { |
304 | for (; index < json.Length; index++) { |
305 | if (" \t\n\r".IndexOf (json[index]) == -1) { |
306 | break; |
307 | } |
308 | } |
309 | } |
310 | |
311 | protected static int LookAhead (char[] json, int index) |
312 | { |
313 | int saveIndex = index; |
314 | return NextToken (json, ref saveIndex); |
315 | } |
316 | |
317 | protected static int NextToken (char[] json, ref int index) |
318 | { |
319 | EatWhitespace (json, ref index); |
320 | |
321 | if (index == json.Length) { |
322 | return JSON.TOKEN_NONE; |
323 | } |
324 | |
325 | char c = json[index]; |
326 | index++; |
327 | switch (c) { |
328 | case '{': |
329 | return JSON.TOKEN_CURLY_OPEN; |
330 | case '}': |
331 | return JSON.TOKEN_CURLY_CLOSE; |
332 | case '[': |
333 | return JSON.TOKEN_SQUARED_OPEN; |
334 | case ']': |
335 | return JSON.TOKEN_SQUARED_CLOSE; |
336 | case ',': |
337 | return JSON.TOKEN_COMMA; |
338 | case '"': |
339 | return JSON.TOKEN_STRING; |
340 | case '0': |
341 | case '1': |
342 | case '2': |
343 | case '3': |
344 | case '4': |
345 | case '5': |
346 | case '6': |
347 | case '7': |
348 | case '8': |
349 | case '9': |
350 | case '-': |
351 | return JSON.TOKEN_NUMBER; |
352 | case ':': |
353 | return JSON.TOKEN_COLON; |
354 | } |
355 | index--; |
356 | |
357 | int remainingLength = json.Length - index; |
358 | |
359 | // false |
360 | if (remainingLength >= 5) { |
361 | if (json[index] == 'f' && json[index + 1] == 'a' && json[index + 2] == 'l' && json[index + 3] == 's' && json[index + 4] == 'e') { |
362 | index += 5; |
363 | return JSON.TOKEN_FALSE; |
364 | } |
365 | } |
366 | |
367 | // true |
368 | if (remainingLength >= 4) { |
369 | if (json[index] == 't' && json[index + 1] == 'r' && json[index + 2] == 'u' && json[index + 3] == 'e') { |
370 | index += 4; |
371 | return JSON.TOKEN_TRUE; |
372 | } |
373 | } |
374 | |
375 | // null |
376 | if (remainingLength >= 4) { |
377 | if (json[index] == 'n' && json[index + 1] == 'u' && json[index + 2] == 'l' && json[index + 3] == 'l') { |
378 | index += 4; |
379 | return JSON.TOKEN_NULL; |
380 | } |
381 | } |
382 | |
383 | return JSON.TOKEN_NONE; |
384 | } |
385 | |
386 | protected static bool SerializeValue (object value, StringBuilder builder) |
387 | { |
388 | bool success = true; |
389 | |
390 | if (value is string) { |
391 | success = SerializeString ((string)value, builder); |
392 | } else if (value is Hashtable) { |
393 | success = SerializeObject ((Hashtable)value, builder); |
394 | } else if (value is ArrayList) { |
395 | success = SerializeArray ((ArrayList)value, builder); |
396 | } else if (IsNumeric (value)) { |
397 | success = SerializeNumber (Convert.ToDouble (value), builder); |
398 | } else if ((value is Boolean) && ((Boolean)value == true)) { |
399 | builder.Append ("true"); |
400 | } else if ((value is Boolean) && ((Boolean)value == false)) { |
401 | builder.Append ("false"); |
402 | } else if (value == null) { |
403 | builder.Append ("null"); |
404 | } else { |
405 | success = false; |
406 | } |
407 | return success; |
408 | } |
409 | |
410 | protected static bool SerializeObject (Hashtable anObject, StringBuilder builder) |
411 | { |
412 | builder.Append ("{"); |
413 | |
414 | IDictionaryEnumerator e = anObject.GetEnumerator (); |
415 | bool first = true; |
416 | while (e.MoveNext ()) { |
417 | string key = e.Key.ToString (); |
418 | object value = e.Value; |
419 | |
420 | if (!first) { |
421 | builder.Append (", "); |
422 | } |
423 | |
424 | SerializeString (key, builder); |
425 | builder.Append (":"); |
426 | if (!SerializeValue (value, builder)) { |
427 | return false; |
428 | } |
429 | |
430 | first = false; |
431 | } |
432 | |
433 | builder.Append ("}"); |
434 | return true; |
435 | } |
436 | |
437 | protected static bool SerializeArray (ArrayList anArray, StringBuilder builder) |
438 | { |
439 | builder.Append ("["); |
440 | |
441 | bool first = true; |
442 | for (int i = 0; i < anArray.Count; i++) { |
443 | object value = anArray[i]; |
444 | |
445 | if (!first) { |
446 | builder.Append (", "); |
447 | } |
448 | |
449 | if (!SerializeValue (value, builder)) { |
450 | return false; |
451 | } |
452 | |
453 | first = false; |
454 | } |
455 | |
456 | builder.Append ("]"); |
457 | return true; |
458 | } |
459 | |
460 | protected static bool SerializeString (string aString, StringBuilder builder) |
461 | { |
462 | builder.Append ("\""); |
463 | |
464 | char[] charArray = aString.ToCharArray (); |
465 | for (int i = 0; i < charArray.Length; i++) { |
466 | char c = charArray[i]; |
467 | if (c == '"') { |
468 | builder.Append ("\\\""); |
469 | } else if (c == '\\') { |
470 | builder.Append ("\\\\"); |
471 | } else if (c == '\b') { |
472 | builder.Append ("\\b"); |
473 | } else if (c == '\f') { |
474 | builder.Append ("\\f"); |
475 | } else if (c == '\n') { |
476 | builder.Append ("\\n"); |
477 | } else if (c == '\r') { |
478 | builder.Append ("\\r"); |
479 | } else if (c == '\t') { |
480 | builder.Append ("\\t"); |
481 | } else { |
482 | int codepoint = Convert.ToInt32 (c); |
483 | if ((codepoint >= 32) && (codepoint <= 126)) { |
484 | builder.Append (c); |
485 | } else { |
486 | builder.Append ("\\u" + Convert.ToString (codepoint, 16).PadLeft (4, '0')); |
487 | } |
488 | } |
489 | } |
490 | |
491 | builder.Append ("\""); |
492 | return true; |
493 | } |
494 | |
495 | protected static bool SerializeNumber (double number, StringBuilder builder) |
496 | { |
497 | builder.Append (Convert.ToString (number, CultureInfo.InvariantCulture)); |
498 | return true; |
499 | } |
500 | |
501 | /// <summary> |
502 | /// Determines if a given object is numeric in any way |
503 | /// (can be integer, double, null, etc). |
504 | /// |
505 | /// Thanks to mtighe for pointing out Double.TryParse to me. |
506 | /// </summary> |
507 | protected static bool IsNumeric (object o) |
508 | { |
509 | double result; |
510 | |
511 | return (o == null) ? false : Double.TryParse (o.ToString (), out result); |
512 | } |
513 | } |
514 |