es.json.parse.js 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. 'use strict';
  2. var $ = require('../internals/export');
  3. var DESCRIPTORS = require('../internals/descriptors');
  4. var globalThis = require('../internals/global-this');
  5. var getBuiltIn = require('../internals/get-built-in');
  6. var uncurryThis = require('../internals/function-uncurry-this');
  7. var call = require('../internals/function-call');
  8. var isCallable = require('../internals/is-callable');
  9. var isObject = require('../internals/is-object');
  10. var isArray = require('../internals/is-array');
  11. var hasOwn = require('../internals/has-own-property');
  12. var toString = require('../internals/to-string');
  13. var lengthOfArrayLike = require('../internals/length-of-array-like');
  14. var createProperty = require('../internals/create-property');
  15. var fails = require('../internals/fails');
  16. var parseJSONString = require('../internals/parse-json-string');
  17. var NATIVE_SYMBOL = require('../internals/symbol-constructor-detection');
  18. var JSON = globalThis.JSON;
  19. var Number = globalThis.Number;
  20. var SyntaxError = globalThis.SyntaxError;
  21. var nativeParse = JSON && JSON.parse;
  22. var enumerableOwnProperties = getBuiltIn('Object', 'keys');
  23. // eslint-disable-next-line es/no-object-getownpropertydescriptor -- safe
  24. var getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
  25. var at = uncurryThis(''.charAt);
  26. var slice = uncurryThis(''.slice);
  27. var exec = uncurryThis(/./.exec);
  28. var push = uncurryThis([].push);
  29. var IS_DIGIT = /^\d$/;
  30. var IS_NON_ZERO_DIGIT = /^[1-9]$/;
  31. var IS_NUMBER_START = /^[\d-]$/;
  32. var IS_WHITESPACE = /^[\t\n\r ]$/;
  33. var PRIMITIVE = 0;
  34. var OBJECT = 1;
  35. var $parse = function (source, reviver) {
  36. source = toString(source);
  37. var context = new Context(source, 0, '');
  38. var root = context.parse();
  39. var value = root.value;
  40. var endIndex = context.skip(IS_WHITESPACE, root.end);
  41. if (endIndex < source.length) {
  42. throw new SyntaxError('Unexpected extra character: "' + at(source, endIndex) + '" after the parsed data at: ' + endIndex);
  43. }
  44. return isCallable(reviver) ? internalize({ '': value }, '', reviver, root) : value;
  45. };
  46. var internalize = function (holder, name, reviver, node) {
  47. var val = holder[name];
  48. var unmodified = node && val === node.value;
  49. var context = unmodified && typeof node.source == 'string' ? { source: node.source } : {};
  50. var elementRecordsLen, keys, len, i, P;
  51. if (isObject(val)) {
  52. var nodeIsArray = isArray(val);
  53. var nodes = unmodified ? node.nodes : nodeIsArray ? [] : {};
  54. if (nodeIsArray) {
  55. elementRecordsLen = nodes.length;
  56. len = lengthOfArrayLike(val);
  57. for (i = 0; i < len; i++) {
  58. internalizeProperty(val, i, internalize(val, '' + i, reviver, i < elementRecordsLen ? nodes[i] : undefined));
  59. }
  60. } else {
  61. keys = enumerableOwnProperties(val);
  62. len = lengthOfArrayLike(keys);
  63. for (i = 0; i < len; i++) {
  64. P = keys[i];
  65. internalizeProperty(val, P, internalize(val, P, reviver, hasOwn(nodes, P) ? nodes[P] : undefined));
  66. }
  67. }
  68. }
  69. return call(reviver, holder, name, val, context);
  70. };
  71. var internalizeProperty = function (object, key, value) {
  72. if (DESCRIPTORS) {
  73. var descriptor = getOwnPropertyDescriptor(object, key);
  74. if (descriptor && !descriptor.configurable) return;
  75. }
  76. if (value === undefined) delete object[key];
  77. else createProperty(object, key, value);
  78. };
  79. var Node = function (value, end, source, nodes) {
  80. this.value = value;
  81. this.end = end;
  82. this.source = source;
  83. this.nodes = nodes;
  84. };
  85. var Context = function (source, index) {
  86. this.source = source;
  87. this.index = index;
  88. };
  89. // https://www.json.org/json-en.html
  90. Context.prototype = {
  91. fork: function (nextIndex) {
  92. return new Context(this.source, nextIndex);
  93. },
  94. parse: function () {
  95. var source = this.source;
  96. var i = this.skip(IS_WHITESPACE, this.index);
  97. var fork = this.fork(i);
  98. var chr = at(source, i);
  99. if (exec(IS_NUMBER_START, chr)) return fork.number();
  100. switch (chr) {
  101. case '{':
  102. return fork.object();
  103. case '[':
  104. return fork.array();
  105. case '"':
  106. return fork.string();
  107. case 't':
  108. return fork.keyword(true);
  109. case 'f':
  110. return fork.keyword(false);
  111. case 'n':
  112. return fork.keyword(null);
  113. } throw new SyntaxError('Unexpected character: "' + chr + '" at: ' + i);
  114. },
  115. node: function (type, value, start, end, nodes) {
  116. return new Node(value, end, type ? null : slice(this.source, start, end), nodes);
  117. },
  118. object: function () {
  119. var source = this.source;
  120. var i = this.index + 1;
  121. var expectKeypair = false;
  122. var object = {};
  123. var nodes = {};
  124. var closed = false;
  125. while (i < source.length) {
  126. i = this.until(['"', '}'], i);
  127. if (at(source, i) === '}' && !expectKeypair) {
  128. i++;
  129. closed = true;
  130. break;
  131. }
  132. // Parsing the key
  133. var result = this.fork(i).string();
  134. var key = result.value;
  135. i = result.end;
  136. i = this.until([':'], i) + 1;
  137. // Parsing value
  138. i = this.skip(IS_WHITESPACE, i);
  139. result = this.fork(i).parse();
  140. createProperty(nodes, key, result);
  141. createProperty(object, key, result.value);
  142. i = this.until([',', '}'], result.end);
  143. var chr = at(source, i);
  144. if (chr === ',') {
  145. expectKeypair = true;
  146. i++;
  147. } else if (chr === '}') {
  148. i++;
  149. closed = true;
  150. break;
  151. }
  152. }
  153. if (!closed) throw new SyntaxError('Unterminated object at: ' + i);
  154. return this.node(OBJECT, object, this.index, i, nodes);
  155. },
  156. array: function () {
  157. var source = this.source;
  158. var i = this.index + 1;
  159. var expectElement = false;
  160. var array = [];
  161. var nodes = [];
  162. var closed = false;
  163. while (i < source.length) {
  164. i = this.skip(IS_WHITESPACE, i);
  165. if (at(source, i) === ']' && !expectElement) {
  166. i++;
  167. closed = true;
  168. break;
  169. }
  170. var result = this.fork(i).parse();
  171. push(nodes, result);
  172. push(array, result.value);
  173. i = this.until([',', ']'], result.end);
  174. if (at(source, i) === ',') {
  175. expectElement = true;
  176. i++;
  177. } else if (at(source, i) === ']') {
  178. i++;
  179. closed = true;
  180. break;
  181. }
  182. }
  183. if (!closed) throw new SyntaxError('Unterminated array at: ' + i);
  184. return this.node(OBJECT, array, this.index, i, nodes);
  185. },
  186. string: function () {
  187. var index = this.index;
  188. var parsed = parseJSONString(this.source, this.index + 1);
  189. return this.node(PRIMITIVE, parsed.value, index, parsed.end);
  190. },
  191. number: function () {
  192. var source = this.source;
  193. var startIndex = this.index;
  194. var i = startIndex;
  195. if (at(source, i) === '-') i++;
  196. if (at(source, i) === '0') i++;
  197. else if (exec(IS_NON_ZERO_DIGIT, at(source, i))) i = this.skip(IS_DIGIT, i + 1);
  198. else throw new SyntaxError('Failed to parse number at: ' + i);
  199. if (at(source, i) === '.') {
  200. var fractionStartIndex = i + 1;
  201. i = this.skip(IS_DIGIT, fractionStartIndex);
  202. if (fractionStartIndex === i) throw new SyntaxError("Failed to parse number's fraction at: " + i);
  203. }
  204. if (at(source, i) === 'e' || at(source, i) === 'E') {
  205. i++;
  206. if (at(source, i) === '+' || at(source, i) === '-') i++;
  207. var exponentStartIndex = i;
  208. i = this.skip(IS_DIGIT, i);
  209. if (exponentStartIndex === i) throw new SyntaxError("Failed to parse number's exponent value at: " + i);
  210. }
  211. return this.node(PRIMITIVE, Number(slice(source, startIndex, i)), startIndex, i);
  212. },
  213. keyword: function (value) {
  214. var keyword = '' + value;
  215. var index = this.index;
  216. var endIndex = index + keyword.length;
  217. if (slice(this.source, index, endIndex) !== keyword) throw new SyntaxError('Failed to parse value at: ' + index);
  218. return this.node(PRIMITIVE, value, index, endIndex);
  219. },
  220. skip: function (regex, i) {
  221. var source = this.source;
  222. for (; i < source.length; i++) if (!exec(regex, at(source, i))) break;
  223. return i;
  224. },
  225. until: function (array, i) {
  226. i = this.skip(IS_WHITESPACE, i);
  227. var chr = at(this.source, i);
  228. for (var j = 0; j < array.length; j++) if (array[j] === chr) return i;
  229. throw new SyntaxError('Unexpected character: "' + chr + '" at: ' + i);
  230. }
  231. };
  232. var NO_SOURCE_SUPPORT = fails(function () {
  233. var unsafeInt = '9007199254740993';
  234. var source;
  235. nativeParse(unsafeInt, function (key, value, context) {
  236. source = context.source;
  237. });
  238. return source !== unsafeInt;
  239. });
  240. var PROPER_BASE_PARSE = NATIVE_SYMBOL && !fails(function () {
  241. // Safari 9 bug
  242. return 1 / nativeParse('-0 \t') !== -Infinity;
  243. });
  244. // `JSON.parse` method
  245. // https://tc39.es/ecma262/#sec-json.parse
  246. // https://github.com/tc39/proposal-json-parse-with-source
  247. $({ target: 'JSON', stat: true, forced: NO_SOURCE_SUPPORT }, {
  248. parse: function parse(text, reviver) {
  249. return PROPER_BASE_PARSE && !isCallable(reviver) ? nativeParse(text) : $parse(text, reviver);
  250. }
  251. });