UNPKG

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