JSONParser.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541
  1. /*
  2. * This is a customized parser, based on "SimpleJson.cs", for JSONNode
  3. *
  4. * SimpleJson.cs is open source software and this entire file may be dealt with as described below:
  5. */
  6. //-----------------------------------------------------------------------
  7. // <copyright file="SimpleJson.cs" company="The Outercurve Foundation">
  8. // Copyright (c) 2011, The Outercurve Foundation, 2015 Zen Fulcrum LLC
  9. //
  10. // Licensed under the MIT License (the "License");
  11. // you may not use this file except in compliance with the License.
  12. // You may obtain a copy of the License at
  13. // http://www.opensource.org/licenses/mit-license.php
  14. //
  15. // Unless required by applicable law or agreed to in writing, software
  16. // distributed under the License is distributed on an "AS IS" BASIS,
  17. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  18. // See the License for the specific language governing permissions and
  19. // limitations under the License.
  20. // </copyright>
  21. // <author>Nathan Totten (ntotten.com), Jim Zimmerman (jimzimmerman.com) and Prabir Shrestha (prabir.me)</author>
  22. // <website>https://github.com/facebook-csharp-sdk/simple-json</website>
  23. //-----------------------------------------------------------------------
  24. // ReSharper disable InconsistentNaming
  25. using System;
  26. using System.Collections;
  27. using System.Collections.Generic;
  28. using System.Globalization;
  29. using System.Runtime.Serialization;
  30. using System.Text;
  31. namespace ZenFulcrum.EmbeddedBrowser {
  32. /// <summary>
  33. /// This class encodes and decodes JSON strings.
  34. /// Spec. details, see http://www.json.org/
  35. ///
  36. /// JSON uses Arrays and Objects. These correspond here to the datatypes JsonArray(IList&lt;object>) and JsonObject(IDictionary&lt;string,object>).
  37. /// All numbers are parsed to doubles.
  38. /// </summary>
  39. internal static class JSONParser {
  40. private const int TOKEN_NONE = 0;
  41. private const int TOKEN_CURLY_OPEN = 1;
  42. private const int TOKEN_CURLY_CLOSE = 2;
  43. private const int TOKEN_SQUARED_OPEN = 3;
  44. private const int TOKEN_SQUARED_CLOSE = 4;
  45. private const int TOKEN_COLON = 5;
  46. private const int TOKEN_COMMA = 6;
  47. private const int TOKEN_STRING = 7;
  48. private const int TOKEN_NUMBER = 8;
  49. private const int TOKEN_TRUE = 9;
  50. private const int TOKEN_FALSE = 10;
  51. private const int TOKEN_NULL = 11;
  52. private const int BUILDER_CAPACITY = 2000;
  53. private static readonly char[] EscapeTable;
  54. private static readonly char[] EscapeCharacters = new char[] { '"', '\\', '\b', '\f', '\n', '\r', '\t' };
  55. // private static readonly string EscapeCharactersString = new string(EscapeCharacters);
  56. static JSONParser() {
  57. EscapeTable = new char[93];
  58. EscapeTable['"'] = '"';
  59. EscapeTable['\\'] = '\\';
  60. EscapeTable['\b'] = 'b';
  61. EscapeTable['\f'] = 'f';
  62. EscapeTable['\n'] = 'n';
  63. EscapeTable['\r'] = 'r';
  64. EscapeTable['\t'] = 't';
  65. }
  66. /// <summary>
  67. /// Parses the string json into a value
  68. /// </summary>
  69. /// <param name="json">A JSON string.</param>
  70. /// <returns>An IList&lt;object>, a IDictionary&lt;string,object>, a double, a string, null, true, or false</returns>
  71. public static JSONNode Parse(string json) {
  72. JSONNode obj;
  73. if (TryDeserializeObject(json, out obj))
  74. return obj;
  75. throw new SerializationException("Invalid JSON string");
  76. }
  77. /// <summary>
  78. /// Try parsing the json string into a value.
  79. /// </summary>
  80. /// <param name="json">
  81. /// A JSON string.
  82. /// </param>
  83. /// <param name="obj">
  84. /// The object.
  85. /// </param>
  86. /// <returns>
  87. /// Returns true if successfull otherwise false.
  88. /// </returns>
  89. public static bool TryDeserializeObject(string json, out JSONNode obj) {
  90. bool success = true;
  91. if (json != null) {
  92. char[] charArray = json.ToCharArray();
  93. int index = 0;
  94. obj = ParseValue(charArray, ref index, ref success);
  95. } else
  96. obj = null;
  97. return success;
  98. }
  99. public static string EscapeToJavascriptString(string jsonString) {
  100. if (string.IsNullOrEmpty(jsonString))
  101. return jsonString;
  102. StringBuilder sb = new StringBuilder();
  103. char c;
  104. for (int i = 0; i < jsonString.Length; ) {
  105. c = jsonString[i++];
  106. if (c == '\\') {
  107. int remainingLength = jsonString.Length - i;
  108. if (remainingLength >= 2) {
  109. char lookahead = jsonString[i];
  110. if (lookahead == '\\') {
  111. sb.Append('\\');
  112. ++i;
  113. } else if (lookahead == '"') {
  114. sb.Append("\"");
  115. ++i;
  116. } else if (lookahead == 't') {
  117. sb.Append('\t');
  118. ++i;
  119. } else if (lookahead == 'b') {
  120. sb.Append('\b');
  121. ++i;
  122. } else if (lookahead == 'n') {
  123. sb.Append('\n');
  124. ++i;
  125. } else if (lookahead == 'r') {
  126. sb.Append('\r');
  127. ++i;
  128. }
  129. }
  130. } else {
  131. sb.Append(c);
  132. }
  133. }
  134. return sb.ToString();
  135. }
  136. static JSONNode ParseObject(char[] json, ref int index, ref bool success) {
  137. JSONNode table = new JSONNode(JSONNode.NodeType.Object);
  138. int token;
  139. // {
  140. NextToken(json, ref index);
  141. bool done = false;
  142. while (!done) {
  143. token = LookAhead(json, index);
  144. if (token == TOKEN_NONE) {
  145. success = false;
  146. return null;
  147. } else if (token == TOKEN_COMMA)
  148. NextToken(json, ref index);
  149. else if (token == TOKEN_CURLY_CLOSE) {
  150. NextToken(json, ref index);
  151. return table;
  152. } else {
  153. // name
  154. string name = ParseString(json, ref index, ref success);
  155. if (!success) {
  156. success = false;
  157. return null;
  158. }
  159. // :
  160. token = NextToken(json, ref index);
  161. if (token != TOKEN_COLON) {
  162. success = false;
  163. return null;
  164. }
  165. // value
  166. JSONNode value = ParseValue(json, ref index, ref success);
  167. if (!success) {
  168. success = false;
  169. return null;
  170. }
  171. table[name] = value;
  172. }
  173. }
  174. return table;
  175. }
  176. static JSONNode ParseArray(char[] json, ref int index, ref bool success) {
  177. JSONNode array = new JSONNode(JSONNode.NodeType.Array);
  178. // [
  179. NextToken(json, ref index);
  180. bool done = false;
  181. while (!done) {
  182. int token = LookAhead(json, index);
  183. if (token == TOKEN_NONE) {
  184. success = false;
  185. return null;
  186. } else if (token == TOKEN_COMMA)
  187. NextToken(json, ref index);
  188. else if (token == TOKEN_SQUARED_CLOSE) {
  189. NextToken(json, ref index);
  190. break;
  191. } else {
  192. JSONNode value = ParseValue(json, ref index, ref success);
  193. if (!success)
  194. return null;
  195. array.Add(value);
  196. }
  197. }
  198. return array;
  199. }
  200. static JSONNode ParseValue(char[] json, ref int index, ref bool success) {
  201. switch (LookAhead(json, index)) {
  202. case TOKEN_STRING:
  203. return ParseString(json, ref index, ref success);
  204. case TOKEN_NUMBER:
  205. return ParseNumber(json, ref index, ref success);
  206. case TOKEN_CURLY_OPEN:
  207. return ParseObject(json, ref index, ref success);
  208. case TOKEN_SQUARED_OPEN:
  209. return ParseArray(json, ref index, ref success);
  210. case TOKEN_TRUE:
  211. NextToken(json, ref index);
  212. return true;
  213. case TOKEN_FALSE:
  214. NextToken(json, ref index);
  215. return false;
  216. case TOKEN_NULL:
  217. NextToken(json, ref index);
  218. return JSONNode.NullNode;
  219. case TOKEN_NONE:
  220. break;
  221. }
  222. success = false;
  223. return JSONNode.InvalidNode;
  224. }
  225. static JSONNode ParseString(char[] json, ref int index, ref bool success) {
  226. StringBuilder s = new StringBuilder(BUILDER_CAPACITY);
  227. char c;
  228. EatWhitespace(json, ref index);
  229. // "
  230. c = json[index++];
  231. bool complete = false;
  232. while (!complete) {
  233. if (index == json.Length)
  234. break;
  235. c = json[index++];
  236. if (c == '"') {
  237. complete = true;
  238. break;
  239. } else if (c == '\\') {
  240. if (index == json.Length)
  241. break;
  242. c = json[index++];
  243. if (c == '"')
  244. s.Append('"');
  245. else if (c == '\\')
  246. s.Append('\\');
  247. else if (c == '/')
  248. s.Append('/');
  249. else if (c == 'b')
  250. s.Append('\b');
  251. else if (c == 'f')
  252. s.Append('\f');
  253. else if (c == 'n')
  254. s.Append('\n');
  255. else if (c == 'r')
  256. s.Append('\r');
  257. else if (c == 't')
  258. s.Append('\t');
  259. else if (c == 'u') {
  260. int remainingLength = json.Length - index;
  261. if (remainingLength >= 4) {
  262. // parse the 32 bit hex into an integer codepoint
  263. uint codePoint;
  264. if (!(success = UInt32.TryParse(new string(json, index, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out codePoint)))
  265. return "";
  266. // convert the integer codepoint to a unicode char and add to string
  267. if (0xD800 <= codePoint && codePoint <= 0xDBFF) // if high surrogate
  268. {
  269. index += 4; // skip 4 chars
  270. remainingLength = json.Length - index;
  271. if (remainingLength >= 6) {
  272. uint lowCodePoint;
  273. if (new string(json, index, 2) == "\\u" && UInt32.TryParse(new string(json, index + 2, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out lowCodePoint)) {
  274. if (0xDC00 <= lowCodePoint && lowCodePoint <= 0xDFFF) // if low surrogate
  275. {
  276. s.Append((char)codePoint);
  277. s.Append((char)lowCodePoint);
  278. index += 6; // skip 6 chars
  279. continue;
  280. }
  281. }
  282. }
  283. success = false; // invalid surrogate pair
  284. return "";
  285. }
  286. s.Append(ConvertFromUtf32((int)codePoint));
  287. // skip 4 chars
  288. index += 4;
  289. } else
  290. break;
  291. }
  292. } else
  293. s.Append(c);
  294. }
  295. if (!complete) {
  296. success = false;
  297. return null;
  298. }
  299. return s.ToString();
  300. }
  301. private static string ConvertFromUtf32(int utf32) {
  302. // http://www.java2s.com/Open-Source/CSharp/2.6.4-mono-.net-core/System/System/Char.cs.htm
  303. if (utf32 < 0 || utf32 > 0x10FFFF)
  304. throw new ArgumentOutOfRangeException("utf32", "The argument must be from 0 to 0x10FFFF.");
  305. if (0xD800 <= utf32 && utf32 <= 0xDFFF)
  306. throw new ArgumentOutOfRangeException("utf32", "The argument must not be in surrogate pair range.");
  307. if (utf32 < 0x10000)
  308. return new string((char)utf32, 1);
  309. utf32 -= 0x10000;
  310. return new string(new char[] { (char)((utf32 >> 10) + 0xD800), (char)(utf32 % 0x0400 + 0xDC00) });
  311. }
  312. static JSONNode ParseNumber(char[] json, ref int index, ref bool success) {
  313. EatWhitespace(json, ref index);
  314. int lastIndex = GetLastIndexOfNumber(json, index);
  315. int charLength = (lastIndex - index) + 1;
  316. JSONNode returnNumber;
  317. string str = new string(json, index, charLength);
  318. if (str.IndexOf(".", StringComparison.OrdinalIgnoreCase) != -1 || str.IndexOf("e", StringComparison.OrdinalIgnoreCase) != -1) {
  319. double number;
  320. success = double.TryParse(new string(json, index, charLength), NumberStyles.Any, CultureInfo.InvariantCulture, out number);
  321. returnNumber = number;
  322. } else {
  323. long number;
  324. success = long.TryParse(new string(json, index, charLength), NumberStyles.Any, CultureInfo.InvariantCulture, out number);
  325. returnNumber = number;
  326. }
  327. index = lastIndex + 1;
  328. return returnNumber;
  329. }
  330. static int GetLastIndexOfNumber(char[] json, int index) {
  331. int lastIndex;
  332. for (lastIndex = index; lastIndex < json.Length; lastIndex++)
  333. if ("0123456789+-.eE".IndexOf(json[lastIndex]) == -1) break;
  334. return lastIndex - 1;
  335. }
  336. static void EatWhitespace(char[] json, ref int index) {
  337. for (; index < json.Length; index++)
  338. if (" \t\n\r\b\f".IndexOf(json[index]) == -1) break;
  339. }
  340. static int LookAhead(char[] json, int index) {
  341. int saveIndex = index;
  342. return NextToken(json, ref saveIndex);
  343. }
  344. static int NextToken(char[] json, ref int index) {
  345. EatWhitespace(json, ref index);
  346. if (index == json.Length)
  347. return TOKEN_NONE;
  348. char c = json[index];
  349. index++;
  350. switch (c) {
  351. case '{':
  352. return TOKEN_CURLY_OPEN;
  353. case '}':
  354. return TOKEN_CURLY_CLOSE;
  355. case '[':
  356. return TOKEN_SQUARED_OPEN;
  357. case ']':
  358. return TOKEN_SQUARED_CLOSE;
  359. case ',':
  360. return TOKEN_COMMA;
  361. case '"':
  362. return TOKEN_STRING;
  363. case '0':
  364. case '1':
  365. case '2':
  366. case '3':
  367. case '4':
  368. case '5':
  369. case '6':
  370. case '7':
  371. case '8':
  372. case '9':
  373. case '-':
  374. return TOKEN_NUMBER;
  375. case ':':
  376. return TOKEN_COLON;
  377. }
  378. index--;
  379. int remainingLength = json.Length - index;
  380. // false
  381. if (remainingLength >= 5) {
  382. if (json[index] == 'f' && json[index + 1] == 'a' && json[index + 2] == 'l' && json[index + 3] == 's' && json[index + 4] == 'e') {
  383. index += 5;
  384. return TOKEN_FALSE;
  385. }
  386. }
  387. // true
  388. if (remainingLength >= 4) {
  389. if (json[index] == 't' && json[index + 1] == 'r' && json[index + 2] == 'u' && json[index + 3] == 'e') {
  390. index += 4;
  391. return TOKEN_TRUE;
  392. }
  393. }
  394. // null
  395. if (remainingLength >= 4) {
  396. if (json[index] == 'n' && json[index + 1] == 'u' && json[index + 2] == 'l' && json[index + 3] == 'l') {
  397. index += 4;
  398. return TOKEN_NULL;
  399. }
  400. }
  401. return TOKEN_NONE;
  402. }
  403. public static string Serialize(JSONNode node) {
  404. StringBuilder sb = new StringBuilder();
  405. var success = SerializeValue(node, sb);
  406. if (!success) throw new SerializationException("Failed to serialize JSON");
  407. return sb.ToString();
  408. }
  409. static bool SerializeValue(JSONNode value, StringBuilder builder) {
  410. bool success = true;
  411. // if (value == null) {
  412. // builder.Append("null");
  413. // return success;
  414. // }
  415. switch (value.Type) {
  416. case JSONNode.NodeType.String:
  417. success = SerializeString(value, builder);
  418. break;
  419. case JSONNode.NodeType.Object: {
  420. Dictionary<String, JSONNode> dict = value;
  421. success = SerializeObject(dict.Keys, dict.Values, builder);
  422. break;
  423. }
  424. case JSONNode.NodeType.Array:
  425. success = SerializeArray((List<JSONNode>)value, builder);
  426. break;
  427. case JSONNode.NodeType.Number:
  428. success = SerializeNumber(value, builder);
  429. break;
  430. case JSONNode.NodeType.Bool:
  431. builder.Append(value ? "true" : "false");
  432. break;
  433. case JSONNode.NodeType.Null:
  434. builder.Append("null");
  435. break;
  436. case JSONNode.NodeType.Invalid:
  437. throw new SerializationException("Cannot serialize invalid JSONNode");
  438. default:
  439. throw new SerializationException("Unknown JSONNode type");
  440. }
  441. return success;
  442. }
  443. static bool SerializeObject(IEnumerable<string> keys, IEnumerable<JSONNode> values, StringBuilder builder) {
  444. builder.Append("{");
  445. var ke = keys.GetEnumerator();
  446. var ve = values.GetEnumerator();
  447. bool first = true;
  448. while (ke.MoveNext() && ve.MoveNext()) {
  449. var key = ke.Current;
  450. var value = ve.Current;
  451. if (!first)
  452. builder.Append(",");
  453. string stringKey = key;
  454. if (stringKey != null)
  455. SerializeString(stringKey, builder);
  456. else
  457. if (!SerializeValue(value, builder)) return false;
  458. builder.Append(":");
  459. if (!SerializeValue(value, builder))
  460. return false;
  461. first = false;
  462. }
  463. builder.Append("}");
  464. return true;
  465. }
  466. static bool SerializeArray(IEnumerable<JSONNode> anArray, StringBuilder builder) {
  467. builder.Append("[");
  468. bool first = true;
  469. foreach (var value in anArray) {
  470. if (!first)
  471. builder.Append(",");
  472. if (!SerializeValue(value, builder))
  473. return false;
  474. first = false;
  475. }
  476. builder.Append("]");
  477. return true;
  478. }
  479. static bool SerializeString(string aString, StringBuilder builder) {
  480. // Happy path if there's nothing to be escaped. IndexOfAny is highly optimized (and unmanaged)
  481. if (aString.IndexOfAny(EscapeCharacters) == -1) {
  482. builder.Append('"');
  483. builder.Append(aString);
  484. builder.Append('"');
  485. return true;
  486. }
  487. builder.Append('"');
  488. int safeCharacterCount = 0;
  489. char[] charArray = aString.ToCharArray();
  490. for (int i = 0; i < charArray.Length; i++) {
  491. char c = charArray[i];
  492. // Non ascii characters are fine, buffer them up and send them to the builder
  493. // in larger chunks if possible. The escape table is a 1:1 translation table
  494. // with \0 [default(char)] denoting a safe character.
  495. if (c >= EscapeTable.Length || EscapeTable[c] == default(char)) {
  496. safeCharacterCount++;
  497. } else {
  498. if (safeCharacterCount > 0) {
  499. builder.Append(charArray, i - safeCharacterCount, safeCharacterCount);
  500. safeCharacterCount = 0;
  501. }
  502. builder.Append('\\');
  503. builder.Append(EscapeTable[c]);
  504. }
  505. }
  506. if (safeCharacterCount > 0) {
  507. builder.Append(charArray, charArray.Length - safeCharacterCount, safeCharacterCount);
  508. }
  509. builder.Append('"');
  510. return true;
  511. }
  512. static bool SerializeNumber(double number, StringBuilder builder) {
  513. builder.Append(Convert.ToDouble(number, CultureInfo.InvariantCulture).ToString("r", CultureInfo.InvariantCulture));
  514. return true;
  515. }
  516. }
  517. }