252 lines
8.2 KiB
JavaScript
252 lines
8.2 KiB
JavaScript
'use strict';
|
|
var $ = require('../internals/export');
|
|
var DESCRIPTORS = require('../internals/descriptors');
|
|
var global = require('../internals/global');
|
|
var getBuiltIn = require('../internals/get-built-in');
|
|
var uncurryThis = require('../internals/function-uncurry-this');
|
|
var call = require('../internals/function-call');
|
|
var isCallable = require('../internals/is-callable');
|
|
var isObject = require('../internals/is-object');
|
|
var isArray = require('../internals/is-array');
|
|
var hasOwn = require('../internals/has-own-property');
|
|
var toString = require('../internals/to-string');
|
|
var lengthOfArrayLike = require('../internals/length-of-array-like');
|
|
var createProperty = require('../internals/create-property');
|
|
var fails = require('../internals/fails');
|
|
var parseJSONString = require('../internals/parse-json-string');
|
|
var NATIVE_SYMBOL = require('../internals/symbol-constructor-detection');
|
|
|
|
var JSON = global.JSON;
|
|
var Number = global.Number;
|
|
var SyntaxError = global.SyntaxError;
|
|
var nativeParse = JSON && JSON.parse;
|
|
var enumerableOwnProperties = getBuiltIn('Object', 'keys');
|
|
// eslint-disable-next-line es/no-object-getownpropertydescriptor -- safe
|
|
var getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
|
|
var at = uncurryThis(''.charAt);
|
|
var slice = uncurryThis(''.slice);
|
|
var exec = uncurryThis(/./.exec);
|
|
var push = uncurryThis([].push);
|
|
|
|
var IS_DIGIT = /^\d$/;
|
|
var IS_NON_ZERO_DIGIT = /^[1-9]$/;
|
|
var IS_NUMBER_START = /^(?:-|\d)$/;
|
|
var IS_WHITESPACE = /^[\t\n\r ]$/;
|
|
|
|
var PRIMITIVE = 0;
|
|
var OBJECT = 1;
|
|
|
|
var $parse = function (source, reviver) {
|
|
source = toString(source);
|
|
var context = new Context(source, 0, '');
|
|
var root = context.parse();
|
|
var value = root.value;
|
|
var endIndex = context.skip(IS_WHITESPACE, root.end);
|
|
if (endIndex < source.length) {
|
|
throw SyntaxError('Unexpected extra character: "' + at(source, endIndex) + '" after the parsed data at: ' + endIndex);
|
|
}
|
|
return isCallable(reviver) ? internalize({ '': value }, '', reviver, root) : value;
|
|
};
|
|
|
|
var internalize = function (holder, name, reviver, node) {
|
|
var val = holder[name];
|
|
var unmodified = node && val === node.value;
|
|
var context = unmodified && typeof node.source == 'string' ? { source: node.source } : {};
|
|
var elementRecordsLen, keys, len, i, P;
|
|
if (isObject(val)) {
|
|
var nodeIsArray = isArray(val);
|
|
var nodes = unmodified ? node.nodes : nodeIsArray ? [] : {};
|
|
if (nodeIsArray) {
|
|
elementRecordsLen = nodes.length;
|
|
len = lengthOfArrayLike(val);
|
|
for (i = 0; i < len; i++) {
|
|
internalizeProperty(val, i, internalize(val, '' + i, reviver, i < elementRecordsLen ? nodes[i] : undefined));
|
|
}
|
|
} else {
|
|
keys = enumerableOwnProperties(val);
|
|
len = lengthOfArrayLike(keys);
|
|
for (i = 0; i < len; i++) {
|
|
P = keys[i];
|
|
internalizeProperty(val, P, internalize(val, P, reviver, hasOwn(nodes, P) ? nodes[P] : undefined));
|
|
}
|
|
}
|
|
}
|
|
return call(reviver, holder, name, val, context);
|
|
};
|
|
|
|
var internalizeProperty = function (object, key, value) {
|
|
if (DESCRIPTORS) {
|
|
var descriptor = getOwnPropertyDescriptor(object, key);
|
|
if (descriptor && !descriptor.configurable) return;
|
|
}
|
|
if (value === undefined) delete object[key];
|
|
else createProperty(object, key, value);
|
|
};
|
|
|
|
var Node = function (value, end, source, nodes) {
|
|
this.value = value;
|
|
this.end = end;
|
|
this.source = source;
|
|
this.nodes = nodes;
|
|
};
|
|
|
|
var Context = function (source, index) {
|
|
this.source = source;
|
|
this.index = index;
|
|
};
|
|
|
|
// https://www.json.org/json-en.html
|
|
Context.prototype = {
|
|
fork: function (nextIndex) {
|
|
return new Context(this.source, nextIndex);
|
|
},
|
|
parse: function () {
|
|
var source = this.source;
|
|
var i = this.skip(IS_WHITESPACE, this.index);
|
|
var fork = this.fork(i);
|
|
var chr = at(source, i);
|
|
if (exec(IS_NUMBER_START, chr)) return fork.number();
|
|
switch (chr) {
|
|
case '{':
|
|
return fork.object();
|
|
case '[':
|
|
return fork.array();
|
|
case '"':
|
|
return fork.string();
|
|
case 't':
|
|
return fork.keyword(true);
|
|
case 'f':
|
|
return fork.keyword(false);
|
|
case 'n':
|
|
return fork.keyword(null);
|
|
} throw SyntaxError('Unexpected character: "' + chr + '" at: ' + i);
|
|
},
|
|
node: function (type, value, start, end, nodes) {
|
|
return new Node(value, end, type ? null : slice(this.source, start, end), nodes);
|
|
},
|
|
object: function () {
|
|
var source = this.source;
|
|
var i = this.index + 1;
|
|
var expectKeypair = false;
|
|
var object = {};
|
|
var nodes = {};
|
|
while (i < source.length) {
|
|
i = this.until(['"', '}'], i);
|
|
if (at(source, i) === '}' && !expectKeypair) {
|
|
i++;
|
|
break;
|
|
}
|
|
// Parsing the key
|
|
var result = this.fork(i).string();
|
|
var key = result.value;
|
|
i = result.end;
|
|
i = this.until([':'], i) + 1;
|
|
// Parsing value
|
|
i = this.skip(IS_WHITESPACE, i);
|
|
result = this.fork(i).parse();
|
|
createProperty(nodes, key, result);
|
|
createProperty(object, key, result.value);
|
|
i = this.until([',', '}'], result.end);
|
|
var chr = at(source, i);
|
|
if (chr === ',') {
|
|
expectKeypair = true;
|
|
i++;
|
|
} else if (chr === '}') {
|
|
i++;
|
|
break;
|
|
}
|
|
}
|
|
return this.node(OBJECT, object, this.index, i, nodes);
|
|
},
|
|
array: function () {
|
|
var source = this.source;
|
|
var i = this.index + 1;
|
|
var expectElement = false;
|
|
var array = [];
|
|
var nodes = [];
|
|
while (i < source.length) {
|
|
i = this.skip(IS_WHITESPACE, i);
|
|
if (at(source, i) === ']' && !expectElement) {
|
|
i++;
|
|
break;
|
|
}
|
|
var result = this.fork(i).parse();
|
|
push(nodes, result);
|
|
push(array, result.value);
|
|
i = this.until([',', ']'], result.end);
|
|
if (at(source, i) === ',') {
|
|
expectElement = true;
|
|
i++;
|
|
} else if (at(source, i) === ']') {
|
|
i++;
|
|
break;
|
|
}
|
|
}
|
|
return this.node(OBJECT, array, this.index, i, nodes);
|
|
},
|
|
string: function () {
|
|
var index = this.index;
|
|
var parsed = parseJSONString(this.source, this.index + 1);
|
|
return this.node(PRIMITIVE, parsed.value, index, parsed.end);
|
|
},
|
|
number: function () {
|
|
var source = this.source;
|
|
var startIndex = this.index;
|
|
var i = startIndex;
|
|
if (at(source, i) === '-') i++;
|
|
if (at(source, i) === '0') i++;
|
|
else if (exec(IS_NON_ZERO_DIGIT, at(source, i))) i = this.skip(IS_DIGIT, ++i);
|
|
else throw SyntaxError('Failed to parse number at: ' + i);
|
|
if (at(source, i) === '.') i = this.skip(IS_DIGIT, ++i);
|
|
if (at(source, i) === 'e' || at(source, i) === 'E') {
|
|
i++;
|
|
if (at(source, i) === '+' || at(source, i) === '-') i++;
|
|
var exponentStartIndex = i;
|
|
i = this.skip(IS_DIGIT, i);
|
|
if (exponentStartIndex === i) throw SyntaxError("Failed to parse number's exponent value at: " + i);
|
|
}
|
|
return this.node(PRIMITIVE, Number(slice(source, startIndex, i)), startIndex, i);
|
|
},
|
|
keyword: function (value) {
|
|
var keyword = '' + value;
|
|
var index = this.index;
|
|
var endIndex = index + keyword.length;
|
|
if (slice(this.source, index, endIndex) !== keyword) throw SyntaxError('Failed to parse value at: ' + index);
|
|
return this.node(PRIMITIVE, value, index, endIndex);
|
|
},
|
|
skip: function (regex, i) {
|
|
var source = this.source;
|
|
for (; i < source.length; i++) if (!exec(regex, at(source, i))) break;
|
|
return i;
|
|
},
|
|
until: function (array, i) {
|
|
i = this.skip(IS_WHITESPACE, i);
|
|
var chr = at(this.source, i);
|
|
for (var j = 0; j < array.length; j++) if (array[j] === chr) return i;
|
|
throw SyntaxError('Unexpected character: "' + chr + '" at: ' + i);
|
|
}
|
|
};
|
|
|
|
var NO_SOURCE_SUPPORT = fails(function () {
|
|
var unsafeInt = '9007199254740993';
|
|
var source;
|
|
nativeParse(unsafeInt, function (key, value, context) {
|
|
source = context.source;
|
|
});
|
|
return source !== unsafeInt;
|
|
});
|
|
|
|
var PROPER_BASE_PARSE = NATIVE_SYMBOL && !fails(function () {
|
|
// Safari 9 bug
|
|
return 1 / nativeParse('-0 \t') !== -Infinity;
|
|
});
|
|
|
|
// `JSON.parse` method
|
|
// https://tc39.es/ecma262/#sec-json.parse
|
|
// https://github.com/tc39/proposal-json-parse-with-source
|
|
$({ target: 'JSON', stat: true, forced: NO_SOURCE_SUPPORT }, {
|
|
parse: function parse(text, reviver) {
|
|
return PROPER_BASE_PARSE && !isCallable(reviver) ? nativeParse(text) : $parse(text, reviver);
|
|
}
|
|
});
|