MiniJSON.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741
  1. using System;
  2. using System.Collections;
  3. using System.Text;
  4. using System.Collections.Generic;
  5. /* Based on the JSON parser from
  6. * http://techblog.procurios.nl/k/618/news/view/14605/14863/How-do-I-write-my-own-parser-for-JSON.html
  7. *
  8. * I simplified it so that it doesn't throw exceptions
  9. * and can be used in Unity iPhone with maximum code stripping.
  10. */
  11. /// <summary>
  12. /// This class encodes and decodes JSON strings.
  13. /// Spec. details, see http://www.json.org/
  14. ///
  15. /// JSON uses Arrays and Objects. These correspond here to the datatypes ArrayList and Hashtable.
  16. /// All numbers are parsed to doubles.
  17. /// </summary>
  18. public class MiniJSON
  19. {
  20. private const int TOKEN_NONE = 0;
  21. private const int TOKEN_CURLY_OPEN = 1;
  22. private const int TOKEN_CURLY_CLOSE = 2;
  23. private const int TOKEN_SQUARED_OPEN = 3;
  24. private const int TOKEN_SQUARED_CLOSE = 4;
  25. private const int TOKEN_COLON = 5;
  26. private const int TOKEN_COMMA = 6;
  27. private const int TOKEN_STRING = 7;
  28. private const int TOKEN_NUMBER = 8;
  29. private const int TOKEN_TRUE = 9;
  30. private const int TOKEN_FALSE = 10;
  31. private const int TOKEN_NULL = 11;
  32. private const int BUILDER_CAPACITY = 2000;
  33. /// <summary>
  34. /// On decoding, this value holds the position at which the parse failed (-1 = no error).
  35. /// </summary>
  36. protected static int lastErrorIndex = -1;
  37. protected static string lastDecode = "";
  38. /// <summary>
  39. /// Parses the string json into a value
  40. /// </summary>
  41. /// <param name="json">A JSON string.</param>
  42. /// <returns>An ArrayList, a Hashtable, a double, a string, null, true, or false</returns>
  43. public static object jsonDecode( string json )
  44. {
  45. // save the string for debug information
  46. MiniJSON.lastDecode = json;
  47. if( json != null )
  48. {
  49. char[] charArray = json.ToCharArray();
  50. int index = 0;
  51. bool success = true;
  52. object value = MiniJSON.parseValue( charArray, ref index, ref success );
  53. if( success )
  54. MiniJSON.lastErrorIndex = -1;
  55. else
  56. MiniJSON.lastErrorIndex = index;
  57. return value;
  58. }
  59. else
  60. {
  61. return null;
  62. }
  63. }
  64. /// <summary>
  65. /// Converts a Hashtable / ArrayList / Dictionary(string,string) object into a JSON string
  66. /// </summary>
  67. /// <param name="json">A Hashtable / ArrayList</param>
  68. /// <returns>A JSON encoded string, or null if object 'json' is not serializable</returns>
  69. public static string jsonEncode( object json )
  70. {
  71. var builder = new StringBuilder( BUILDER_CAPACITY );
  72. var success = MiniJSON.serializeValue( json, builder );
  73. return ( success ? builder.ToString() : null );
  74. }
  75. /// <summary>
  76. /// On decoding, this function returns the position at which the parse failed (-1 = no error).
  77. /// </summary>
  78. /// <returns></returns>
  79. public static bool lastDecodeSuccessful()
  80. {
  81. return ( MiniJSON.lastErrorIndex == -1 );
  82. }
  83. /// <summary>
  84. /// On decoding, this function returns the position at which the parse failed (-1 = no error).
  85. /// </summary>
  86. /// <returns></returns>
  87. public static int getLastErrorIndex()
  88. {
  89. return MiniJSON.lastErrorIndex;
  90. }
  91. /// <summary>
  92. /// If a decoding error occurred, this function returns a piece of the JSON string
  93. /// at which the error took place. To ease debugging.
  94. /// </summary>
  95. /// <returns></returns>
  96. public static string getLastErrorSnippet()
  97. {
  98. if( MiniJSON.lastErrorIndex == -1 )
  99. {
  100. return "";
  101. }
  102. else
  103. {
  104. int startIndex = MiniJSON.lastErrorIndex - 5;
  105. int endIndex = MiniJSON.lastErrorIndex + 15;
  106. if( startIndex < 0 )
  107. startIndex = 0;
  108. if( endIndex >= MiniJSON.lastDecode.Length )
  109. endIndex = MiniJSON.lastDecode.Length - 1;
  110. return MiniJSON.lastDecode.Substring( startIndex, endIndex - startIndex + 1 );
  111. }
  112. }
  113. #region Parsing
  114. protected static Hashtable parseObject( char[] json, ref int index )
  115. {
  116. Hashtable table = new Hashtable();
  117. int token;
  118. // {
  119. nextToken( json, ref index );
  120. bool done = false;
  121. while( !done )
  122. {
  123. token = lookAhead( json, index );
  124. if( token == MiniJSON.TOKEN_NONE )
  125. {
  126. return null;
  127. }
  128. else if( token == MiniJSON.TOKEN_COMMA )
  129. {
  130. nextToken( json, ref index );
  131. }
  132. else if( token == MiniJSON.TOKEN_CURLY_CLOSE )
  133. {
  134. nextToken( json, ref index );
  135. return table;
  136. }
  137. else
  138. {
  139. // name
  140. string name = parseString( json, ref index );
  141. if( name == null )
  142. {
  143. return null;
  144. }
  145. // :
  146. token = nextToken( json, ref index );
  147. if( token != MiniJSON.TOKEN_COLON )
  148. return null;
  149. // value
  150. bool success = true;
  151. object value = parseValue( json, ref index, ref success );
  152. if( !success )
  153. return null;
  154. table[name] = value;
  155. }
  156. }
  157. return table;
  158. }
  159. protected static ArrayList parseArray( char[] json, ref int index )
  160. {
  161. ArrayList array = new ArrayList();
  162. // [
  163. nextToken( json, ref index );
  164. bool done = false;
  165. while( !done )
  166. {
  167. int token = lookAhead( json, index );
  168. if( token == MiniJSON.TOKEN_NONE )
  169. {
  170. return null;
  171. }
  172. else if( token == MiniJSON.TOKEN_COMMA )
  173. {
  174. nextToken( json, ref index );
  175. }
  176. else if( token == MiniJSON.TOKEN_SQUARED_CLOSE )
  177. {
  178. nextToken( json, ref index );
  179. break;
  180. }
  181. else
  182. {
  183. bool success = true;
  184. object value = parseValue( json, ref index, ref success );
  185. if( !success )
  186. return null;
  187. array.Add( value );
  188. }
  189. }
  190. return array;
  191. }
  192. protected static object parseValue( char[] json, ref int index, ref bool success )
  193. {
  194. switch( lookAhead( json, index ) )
  195. {
  196. case MiniJSON.TOKEN_STRING:
  197. return parseString( json, ref index );
  198. case MiniJSON.TOKEN_NUMBER:
  199. return parseNumber( json, ref index );
  200. case MiniJSON.TOKEN_CURLY_OPEN:
  201. return parseObject( json, ref index );
  202. case MiniJSON.TOKEN_SQUARED_OPEN:
  203. return parseArray( json, ref index );
  204. case MiniJSON.TOKEN_TRUE:
  205. nextToken( json, ref index );
  206. return Boolean.Parse( "TRUE" );
  207. case MiniJSON.TOKEN_FALSE:
  208. nextToken( json, ref index );
  209. return Boolean.Parse( "FALSE" );
  210. case MiniJSON.TOKEN_NULL:
  211. nextToken( json, ref index );
  212. return null;
  213. case MiniJSON.TOKEN_NONE:
  214. break;
  215. }
  216. success = false;
  217. return null;
  218. }
  219. protected static string parseString( char[] json, ref int index )
  220. {
  221. string s = "";
  222. char c;
  223. eatWhitespace( json, ref index );
  224. // "
  225. c = json[index++];
  226. bool complete = false;
  227. while( !complete )
  228. {
  229. if( index == json.Length )
  230. break;
  231. c = json[index++];
  232. if( c == '"' )
  233. {
  234. complete = true;
  235. break;
  236. }
  237. else if( c == '\\' )
  238. {
  239. if( index == json.Length )
  240. break;
  241. c = json[index++];
  242. if( c == '"' )
  243. {
  244. s += '"';
  245. }
  246. else if( c == '\\' )
  247. {
  248. s += '\\';
  249. }
  250. else if( c == '/' )
  251. {
  252. s += '/';
  253. }
  254. else if( c == 'b' )
  255. {
  256. s += '\b';
  257. }
  258. else if( c == 'f' )
  259. {
  260. s += '\f';
  261. }
  262. else if( c == 'n' )
  263. {
  264. s += '\n';
  265. }
  266. else if( c == 'r' )
  267. {
  268. s += '\r';
  269. }
  270. else if( c == 't' )
  271. {
  272. s += '\t';
  273. }
  274. else if( c == 'u' )
  275. {
  276. int remainingLength = json.Length - index;
  277. if( remainingLength >= 4 )
  278. {
  279. char[] unicodeCharArray = new char[4];
  280. Array.Copy( json, index, unicodeCharArray, 0, 4 );
  281. uint codePoint = UInt32.Parse( new string( unicodeCharArray ), System.Globalization.NumberStyles.HexNumber );
  282. // convert the integer codepoint to a unicode char and add to string
  283. s += Char.ConvertFromUtf32( (int)codePoint );
  284. // skip 4 chars
  285. index += 4;
  286. }
  287. else
  288. {
  289. break;
  290. }
  291. }
  292. }
  293. else
  294. {
  295. s += c;
  296. }
  297. }
  298. if( !complete )
  299. return null;
  300. return s;
  301. }
  302. protected static double parseNumber( char[] json, ref int index )
  303. {
  304. eatWhitespace( json, ref index );
  305. int lastIndex = getLastIndexOfNumber( json, index );
  306. int charLength = ( lastIndex - index ) + 1;
  307. char[] numberCharArray = new char[charLength];
  308. Array.Copy( json, index, numberCharArray, 0, charLength );
  309. index = lastIndex + 1;
  310. return Double.Parse( new string( numberCharArray ) ); // , CultureInfo.InvariantCulture);
  311. }
  312. protected static int getLastIndexOfNumber( char[] json, int index )
  313. {
  314. int lastIndex;
  315. for( lastIndex = index; lastIndex < json.Length; lastIndex++ )
  316. if( "0123456789+-.eE".IndexOf( json[lastIndex] ) == -1 )
  317. {
  318. break;
  319. }
  320. return lastIndex - 1;
  321. }
  322. protected static void eatWhitespace( char[] json, ref int index )
  323. {
  324. for( ; index < json.Length; index++ )
  325. if( " \t\n\r".IndexOf( json[index] ) == -1 )
  326. {
  327. break;
  328. }
  329. }
  330. protected static int lookAhead( char[] json, int index )
  331. {
  332. int saveIndex = index;
  333. return nextToken( json, ref saveIndex );
  334. }
  335. protected static int nextToken( char[] json, ref int index )
  336. {
  337. eatWhitespace( json, ref index );
  338. if( index == json.Length )
  339. {
  340. return MiniJSON.TOKEN_NONE;
  341. }
  342. char c = json[index];
  343. index++;
  344. switch( c )
  345. {
  346. case '{':
  347. return MiniJSON.TOKEN_CURLY_OPEN;
  348. case '}':
  349. return MiniJSON.TOKEN_CURLY_CLOSE;
  350. case '[':
  351. return MiniJSON.TOKEN_SQUARED_OPEN;
  352. case ']':
  353. return MiniJSON.TOKEN_SQUARED_CLOSE;
  354. case ',':
  355. return MiniJSON.TOKEN_COMMA;
  356. case '"':
  357. return MiniJSON.TOKEN_STRING;
  358. case '0':
  359. case '1':
  360. case '2':
  361. case '3':
  362. case '4':
  363. case '5':
  364. case '6':
  365. case '7':
  366. case '8':
  367. case '9':
  368. case '-':
  369. return MiniJSON.TOKEN_NUMBER;
  370. case ':':
  371. return MiniJSON.TOKEN_COLON;
  372. }
  373. index--;
  374. int remainingLength = json.Length - index;
  375. // false
  376. if( remainingLength >= 5 )
  377. {
  378. if( json[index] == 'f' &&
  379. json[index + 1] == 'a' &&
  380. json[index + 2] == 'l' &&
  381. json[index + 3] == 's' &&
  382. json[index + 4] == 'e' )
  383. {
  384. index += 5;
  385. return MiniJSON.TOKEN_FALSE;
  386. }
  387. }
  388. // true
  389. if( remainingLength >= 4 )
  390. {
  391. if( json[index] == 't' &&
  392. json[index + 1] == 'r' &&
  393. json[index + 2] == 'u' &&
  394. json[index + 3] == 'e' )
  395. {
  396. index += 4;
  397. return MiniJSON.TOKEN_TRUE;
  398. }
  399. }
  400. // null
  401. if( remainingLength >= 4 )
  402. {
  403. if( json[index] == 'n' &&
  404. json[index + 1] == 'u' &&
  405. json[index + 2] == 'l' &&
  406. json[index + 3] == 'l' )
  407. {
  408. index += 4;
  409. return MiniJSON.TOKEN_NULL;
  410. }
  411. }
  412. return MiniJSON.TOKEN_NONE;
  413. }
  414. #endregion
  415. #region Serialization
  416. protected static bool serializeObjectOrArray( object objectOrArray, StringBuilder builder )
  417. {
  418. if( objectOrArray is Hashtable )
  419. {
  420. return serializeObject( (Hashtable)objectOrArray, builder );
  421. }
  422. else if( objectOrArray is ArrayList )
  423. {
  424. return serializeArray( (ArrayList)objectOrArray, builder );
  425. }
  426. else
  427. {
  428. return false;
  429. }
  430. }
  431. protected static bool serializeObject( Hashtable anObject, StringBuilder builder )
  432. {
  433. builder.Append( "{" );
  434. IDictionaryEnumerator e = anObject.GetEnumerator();
  435. bool first = true;
  436. while( e.MoveNext() )
  437. {
  438. string key = e.Key.ToString();
  439. object value = e.Value;
  440. if( !first )
  441. {
  442. builder.Append( ", " );
  443. }
  444. serializeString( key, builder );
  445. builder.Append( ":" );
  446. if( !serializeValue( value, builder ) )
  447. {
  448. return false;
  449. }
  450. first = false;
  451. }
  452. builder.Append( "}" );
  453. return true;
  454. }
  455. protected static bool serializeDictionary( Dictionary<string,string> dict, StringBuilder builder )
  456. {
  457. builder.Append( "{" );
  458. bool first = true;
  459. foreach( var kv in dict )
  460. {
  461. if( !first )
  462. builder.Append( ", " );
  463. serializeString( kv.Key, builder );
  464. builder.Append( ":" );
  465. serializeString( kv.Value, builder );
  466. first = false;
  467. }
  468. builder.Append( "}" );
  469. return true;
  470. }
  471. protected static bool serializeArray( ArrayList anArray, StringBuilder builder )
  472. {
  473. builder.Append( "[" );
  474. bool first = true;
  475. for( int i = 0; i < anArray.Count; i++ )
  476. {
  477. object value = anArray[i];
  478. if( !first )
  479. {
  480. builder.Append( ", " );
  481. }
  482. if( !serializeValue( value, builder ) )
  483. {
  484. return false;
  485. }
  486. first = false;
  487. }
  488. builder.Append( "]" );
  489. return true;
  490. }
  491. protected static bool serializeValue( object value, StringBuilder builder )
  492. {
  493. //Type t = value.GetType();
  494. //UnityEngine.Debug.Log("type: " + t.ToString() + " isArray: " + t.IsArray);
  495. if( value == null )
  496. {
  497. builder.Append( "null" );
  498. }
  499. else if( value.GetType().IsArray )
  500. {
  501. serializeArray( new ArrayList( (ICollection)value ), builder );
  502. }
  503. else if( value is string )
  504. {
  505. serializeString( (string)value, builder );
  506. }
  507. else if( value is Char )
  508. {
  509. serializeString( Convert.ToString( (char)value ), builder );
  510. }
  511. else if( value is decimal )
  512. {
  513. serializeString( Convert.ToString( (decimal)value ), builder );
  514. }
  515. else if( value is Hashtable )
  516. {
  517. serializeObject( (Hashtable)value, builder );
  518. }
  519. else if( value is Dictionary<string,string> )
  520. {
  521. serializeDictionary( (Dictionary<string,string>)value, builder );
  522. }
  523. else if( value is ArrayList )
  524. {
  525. serializeArray( (ArrayList)value, builder );
  526. }
  527. else if( ( value is Boolean ) && ( (Boolean)value == true ) )
  528. {
  529. builder.Append( "true" );
  530. }
  531. else if( ( value is Boolean ) && ( (Boolean)value == false ) )
  532. {
  533. builder.Append( "false" );
  534. }
  535. else if( value.GetType().IsPrimitive )
  536. {
  537. serializeNumber( Convert.ToDouble( value ), builder );
  538. }
  539. else
  540. {
  541. return false;
  542. }
  543. return true;
  544. }
  545. protected static void serializeString( string aString, StringBuilder builder )
  546. {
  547. builder.Append( "\"" );
  548. char[] charArray = aString.ToCharArray();
  549. for( int i = 0; i < charArray.Length; i++ )
  550. {
  551. char c = charArray[i];
  552. if( c == '"' )
  553. {
  554. builder.Append( "\\\"" );
  555. }
  556. else if( c == '\\' )
  557. {
  558. builder.Append( "\\\\" );
  559. }
  560. else if( c == '\b' )
  561. {
  562. builder.Append( "\\b" );
  563. }
  564. else if( c == '\f' )
  565. {
  566. builder.Append( "\\f" );
  567. }
  568. else if( c == '\n' )
  569. {
  570. builder.Append( "\\n" );
  571. }
  572. else if( c == '\r' )
  573. {
  574. builder.Append( "\\r" );
  575. }
  576. else if( c == '\t' )
  577. {
  578. builder.Append( "\\t" );
  579. }
  580. else
  581. {
  582. int codepoint = Convert.ToInt32( c );
  583. if( ( codepoint >= 32 ) && ( codepoint <= 126 ) )
  584. {
  585. builder.Append( c );
  586. }
  587. else
  588. {
  589. builder.Append( "\\u" + Convert.ToString( codepoint, 16 ).PadLeft( 4, '0' ) );
  590. }
  591. }
  592. }
  593. builder.Append( "\"" );
  594. }
  595. protected static void serializeNumber( double number, StringBuilder builder )
  596. {
  597. builder.Append( Convert.ToString( number ) ); // , CultureInfo.InvariantCulture));
  598. }
  599. #endregion
  600. }
  601. #region Extension methods
  602. public static class MiniJsonExtensions
  603. {
  604. public static string toJson( this Hashtable obj )
  605. {
  606. return MiniJSON.jsonEncode( obj );
  607. }
  608. public static string toJson( this Dictionary<string,string> obj )
  609. {
  610. return MiniJSON.jsonEncode( obj );
  611. }
  612. public static ArrayList arrayListFromJson( this string json )
  613. {
  614. return MiniJSON.jsonDecode( json ) as ArrayList;
  615. }
  616. public static Hashtable hashtableFromJson( this string json )
  617. {
  618. return MiniJSON.jsonDecode( json ) as Hashtable;
  619. }
  620. }
  621. #endregion