evaluateJson.ts 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. import type {JsonToken} from './constants';
  2. import {
  3. ARR,
  4. ARR_VAL,
  5. ARR_VAL_COMPLETED,
  6. ARR_VAL_STR,
  7. OBJ,
  8. OBJ_KEY,
  9. OBJ_KEY_STR,
  10. OBJ_VAL,
  11. OBJ_VAL_COMPLETED,
  12. OBJ_VAL_STR,
  13. } from './constants';
  14. /**
  15. * Evaluate an (incomplete) JSON string.
  16. */
  17. export function evaluateJson(json: string): JsonToken[] {
  18. const stack: JsonToken[] = [];
  19. for (let pos = 0; pos < json.length; pos++) {
  20. _evaluateJsonPos(stack, json, pos);
  21. }
  22. return stack;
  23. }
  24. function _evaluateJsonPos(stack: JsonToken[], json: string, pos: number): void {
  25. const curStep = stack[stack.length - 1];
  26. const char = json[pos];
  27. const whitespaceRegex = /\s/;
  28. if (whitespaceRegex.test(char)) {
  29. return;
  30. }
  31. if (char === '"' && !_isEscaped(json, pos)) {
  32. _handleQuote(stack, curStep);
  33. return;
  34. }
  35. // eslint-disable-next-line default-case
  36. switch (char) {
  37. case '{':
  38. _handleObj(stack, curStep);
  39. break;
  40. case '[':
  41. _handleArr(stack, curStep);
  42. break;
  43. case ':':
  44. _handleColon(stack, curStep);
  45. break;
  46. case ',':
  47. _handleComma(stack, curStep);
  48. break;
  49. case '}':
  50. _handleObjClose(stack, curStep);
  51. break;
  52. case ']':
  53. _handleArrClose(stack, curStep);
  54. break;
  55. }
  56. }
  57. function _handleQuote(stack: JsonToken[], curStep: JsonToken): void {
  58. // End of obj value
  59. if (curStep === OBJ_VAL_STR) {
  60. stack.pop();
  61. stack.push(OBJ_VAL_COMPLETED);
  62. return;
  63. }
  64. // End of arr value
  65. if (curStep === ARR_VAL_STR) {
  66. stack.pop();
  67. stack.push(ARR_VAL_COMPLETED);
  68. return;
  69. }
  70. // Start of obj value
  71. if (curStep === OBJ_VAL) {
  72. stack.push(OBJ_VAL_STR);
  73. return;
  74. }
  75. // Start of arr value
  76. if (curStep === ARR_VAL) {
  77. stack.push(ARR_VAL_STR);
  78. return;
  79. }
  80. // Start of obj key
  81. if (curStep === OBJ) {
  82. stack.push(OBJ_KEY_STR);
  83. return;
  84. }
  85. // End of obj key
  86. if (curStep === OBJ_KEY_STR) {
  87. stack.pop();
  88. stack.push(OBJ_KEY);
  89. return;
  90. }
  91. }
  92. function _handleObj(stack: JsonToken[], curStep: JsonToken): void {
  93. // Initial object
  94. if (!curStep) {
  95. stack.push(OBJ);
  96. return;
  97. }
  98. // New object as obj value
  99. if (curStep === OBJ_VAL) {
  100. stack.push(OBJ);
  101. return;
  102. }
  103. // New object as array element
  104. if (curStep === ARR_VAL) {
  105. stack.push(OBJ);
  106. }
  107. // New object as first array element
  108. if (curStep === ARR) {
  109. stack.push(OBJ);
  110. return;
  111. }
  112. }
  113. function _handleArr(stack: JsonToken[], curStep: JsonToken): void {
  114. // Initial array
  115. if (!curStep) {
  116. stack.push(ARR);
  117. stack.push(ARR_VAL);
  118. return;
  119. }
  120. // New array as obj value
  121. if (curStep === OBJ_VAL) {
  122. stack.push(ARR);
  123. stack.push(ARR_VAL);
  124. return;
  125. }
  126. // New array as array element
  127. if (curStep === ARR_VAL) {
  128. stack.push(ARR);
  129. stack.push(ARR_VAL);
  130. }
  131. // New array as first array element
  132. if (curStep === ARR) {
  133. stack.push(ARR);
  134. stack.push(ARR_VAL);
  135. return;
  136. }
  137. }
  138. function _handleColon(stack: JsonToken[], curStep: JsonToken): void {
  139. if (curStep === OBJ_KEY) {
  140. stack.pop();
  141. stack.push(OBJ_VAL);
  142. }
  143. }
  144. function _handleComma(stack: JsonToken[], curStep: JsonToken): void {
  145. // Comma after obj value
  146. if (curStep === OBJ_VAL) {
  147. stack.pop();
  148. return;
  149. }
  150. if (curStep === OBJ_VAL_COMPLETED) {
  151. // Pop OBJ_VAL_COMPLETED & OBJ_VAL
  152. stack.pop();
  153. stack.pop();
  154. return;
  155. }
  156. // Comma after arr value
  157. if (curStep === ARR_VAL) {
  158. // do nothing - basically we'd pop ARR_VAL but add it right back
  159. return;
  160. }
  161. if (curStep === ARR_VAL_COMPLETED) {
  162. // Pop ARR_VAL_COMPLETED
  163. stack.pop();
  164. // basically we'd pop ARR_VAL but add it right back
  165. return;
  166. }
  167. }
  168. function _handleObjClose(stack: JsonToken[], curStep: JsonToken): void {
  169. // Empty object {}
  170. if (curStep === OBJ) {
  171. stack.pop();
  172. }
  173. // Object with element
  174. if (curStep === OBJ_VAL) {
  175. // Pop OBJ_VAL, OBJ
  176. stack.pop();
  177. stack.pop();
  178. }
  179. // Obj with element
  180. if (curStep === OBJ_VAL_COMPLETED) {
  181. // Pop OBJ_VAL_COMPLETED, OBJ_VAL, OBJ
  182. stack.pop();
  183. stack.pop();
  184. stack.pop();
  185. }
  186. // if was obj value, complete it
  187. if (stack[stack.length - 1] === OBJ_VAL) {
  188. stack.push(OBJ_VAL_COMPLETED);
  189. }
  190. // if was arr value, complete it
  191. if (stack[stack.length - 1] === ARR_VAL) {
  192. stack.push(ARR_VAL_COMPLETED);
  193. }
  194. }
  195. function _handleArrClose(stack: JsonToken[], curStep: JsonToken): void {
  196. // Empty array []
  197. if (curStep === ARR) {
  198. stack.pop();
  199. }
  200. // Array with element
  201. if (curStep === ARR_VAL) {
  202. // Pop ARR_VAL, ARR
  203. stack.pop();
  204. stack.pop();
  205. }
  206. // Array with element
  207. if (curStep === ARR_VAL_COMPLETED) {
  208. // Pop ARR_VAL_COMPLETED, ARR_VAL, ARR
  209. stack.pop();
  210. stack.pop();
  211. stack.pop();
  212. }
  213. // if was obj value, complete it
  214. if (stack[stack.length - 1] === OBJ_VAL) {
  215. stack.push(OBJ_VAL_COMPLETED);
  216. }
  217. // if was arr value, complete it
  218. if (stack[stack.length - 1] === ARR_VAL) {
  219. stack.push(ARR_VAL_COMPLETED);
  220. }
  221. }
  222. function _isEscaped(str: string, pos: number): boolean {
  223. const previousChar = str[pos - 1];
  224. return previousChar === '\\' && !_isEscaped(str, pos - 1);
  225. }