123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261 |
- 'use strict';
- var at; // The index of the current character
- var ch; // The current character
- var escapee = {
- '"': '"',
- '\\': '\\',
- '/': '/',
- b: '\b',
- f: '\f',
- n: '\n',
- r: '\r',
- t: '\t'
- };
- var text;
- // Call error when something is wrong.
- function error(m) {
- throw {
- name: 'SyntaxError',
- message: m,
- at: at,
- text: text
- };
- }
- function next(c) {
- // If a c parameter is provided, verify that it matches the current character.
- if (c && c !== ch) {
- error("Expected '" + c + "' instead of '" + ch + "'");
- }
- // Get the next character. When there are no more characters, return the empty string.
- ch = text.charAt(at);
- at += 1;
- return ch;
- }
- function number() {
- // Parse a number value.
- var num;
- var str = '';
- if (ch === '-') {
- str = '-';
- next('-');
- }
- while (ch >= '0' && ch <= '9') {
- str += ch;
- next();
- }
- if (ch === '.') {
- str += '.';
- while (next() && ch >= '0' && ch <= '9') {
- str += ch;
- }
- }
- if (ch === 'e' || ch === 'E') {
- str += ch;
- next();
- if (ch === '-' || ch === '+') {
- str += ch;
- next();
- }
- while (ch >= '0' && ch <= '9') {
- str += ch;
- next();
- }
- }
- num = Number(str);
- if (!isFinite(num)) {
- error('Bad number');
- }
- return num;
- }
- function string() {
- // Parse a string value.
- var hex;
- var i;
- var str = '';
- var uffff;
- // When parsing for string values, we must look for " and \ characters.
- if (ch === '"') {
- while (next()) {
- if (ch === '"') {
- next();
- return str;
- } else if (ch === '\\') {
- next();
- if (ch === 'u') {
- uffff = 0;
- for (i = 0; i < 4; i += 1) {
- hex = parseInt(next(), 16);
- if (!isFinite(hex)) {
- break;
- }
- uffff = (uffff * 16) + hex;
- }
- str += String.fromCharCode(uffff);
- } else if (typeof escapee[ch] === 'string') {
- str += escapee[ch];
- } else {
- break;
- }
- } else {
- str += ch;
- }
- }
- }
- error('Bad string');
- }
- // Skip whitespace.
- function white() {
- while (ch && ch <= ' ') {
- next();
- }
- }
- // true, false, or null.
- function word() {
- switch (ch) {
- case 't':
- next('t');
- next('r');
- next('u');
- next('e');
- return true;
- case 'f':
- next('f');
- next('a');
- next('l');
- next('s');
- next('e');
- return false;
- case 'n':
- next('n');
- next('u');
- next('l');
- next('l');
- return null;
- default:
- error("Unexpected '" + ch + "'");
- }
- }
- // Parse an array value.
- function array() {
- var arr = [];
- if (ch === '[') {
- next('[');
- white();
- if (ch === ']') {
- next(']');
- return arr; // empty array
- }
- while (ch) {
- arr.push(value()); // eslint-disable-line no-use-before-define
- white();
- if (ch === ']') {
- next(']');
- return arr;
- }
- next(',');
- white();
- }
- }
- error('Bad array');
- }
- // Parse an object value.
- function object() {
- var key;
- var obj = {};
- if (ch === '{') {
- next('{');
- white();
- if (ch === '}') {
- next('}');
- return obj; // empty object
- }
- while (ch) {
- key = string();
- white();
- next(':');
- if (Object.prototype.hasOwnProperty.call(obj, key)) {
- error('Duplicate key "' + key + '"');
- }
- obj[key] = value(); // eslint-disable-line no-use-before-define
- white();
- if (ch === '}') {
- next('}');
- return obj;
- }
- next(',');
- white();
- }
- }
- error('Bad object');
- }
- // Parse a JSON value. It could be an object, an array, a string, a number, or a word.
- function value() {
- white();
- switch (ch) {
- case '{':
- return object();
- case '[':
- return array();
- case '"':
- return string();
- case '-':
- return number();
- default:
- return ch >= '0' && ch <= '9' ? number() : word();
- }
- }
- // Return the json_parse function. It will have access to all of the above functions and variables.
- module.exports = function (source, reviver) {
- var result;
- text = source;
- at = 0;
- ch = ' ';
- result = value();
- white();
- if (ch) {
- error('Syntax error');
- }
- // If there is a reviver function, we recursively walk the new structure,
- // passing each name/value pair to the reviver function for possible
- // transformation, starting with a temporary root object that holds the result
- // in an empty key. If there is not a reviver function, we simply return the
- // result.
- return typeof reviver === 'function' ? (function walk(holder, key) {
- var k;
- var v;
- var val = holder[key];
- if (val && typeof val === 'object') {
- for (k in value) {
- if (Object.prototype.hasOwnProperty.call(val, k)) {
- v = walk(val, k);
- if (typeof v === 'undefined') {
- delete val[k];
- } else {
- val[k] = v;
- }
- }
- }
- }
- return reviver.call(holder, key, val);
- }({ '': result }, '')) : result;
- };
|