Statistics
| Branch: | Tag: | Revision:

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