mirror of
https://github.com/whoisclebs/lodash.git
synced 2026-02-01 07:47:49 +00:00
783 lines
36 KiB
JavaScript
783 lines
36 KiB
JavaScript
/*! JSON v3.2.4 | http://bestiejs.github.com/json3 | Copyright 2012, Kit Cambridge | http://kit.mit-license.org */
|
|
;(function () {
|
|
// Convenience aliases.
|
|
var getClass = {}.toString, isProperty, forEach, undef;
|
|
|
|
// Detect the `define` function exposed by asynchronous module loaders. The
|
|
// strict `define` check is necessary for compatibility with `r.js`.
|
|
var isLoader = typeof define === "function" && define.amd, JSON3 = !isLoader && typeof exports == "object" && exports;
|
|
|
|
if (JSON3 || isLoader) {
|
|
if (typeof JSON == "object" && JSON) {
|
|
// Delegate to the native `stringify` and `parse` implementations in
|
|
// asynchronous module loaders and CommonJS environments.
|
|
if (isLoader) {
|
|
JSON3 = JSON;
|
|
} else {
|
|
JSON3.stringify = JSON.stringify;
|
|
JSON3.parse = JSON.parse;
|
|
}
|
|
} else if (isLoader) {
|
|
JSON3 = this.JSON = {};
|
|
}
|
|
} else {
|
|
// Export for web browsers and JavaScript engines.
|
|
JSON3 = this.JSON || (this.JSON = {});
|
|
}
|
|
|
|
// Local variables.
|
|
var Escapes, toPaddedString, quote, serialize;
|
|
var fromCharCode, Unescapes, abort, lex, get, walk, update, Index, Source;
|
|
|
|
// Test the `Date#getUTC*` methods. Based on work by @Yaffle.
|
|
var isExtended = new Date(-3509827334573292), floor, Months, getDay;
|
|
|
|
try {
|
|
// The `getUTCFullYear`, `Month`, and `Date` methods return nonsensical
|
|
// results for certain dates in Opera >= 10.53.
|
|
isExtended = isExtended.getUTCFullYear() == -109252 && isExtended.getUTCMonth() === 0 && isExtended.getUTCDate() == 1 &&
|
|
// Safari < 2.0.2 stores the internal millisecond time value correctly,
|
|
// but clips the values returned by the date methods to the range of
|
|
// signed 32-bit integers ([-2 ** 31, 2 ** 31 - 1]).
|
|
isExtended.getUTCHours() == 10 && isExtended.getUTCMinutes() == 37 && isExtended.getUTCSeconds() == 6 && isExtended.getUTCMilliseconds() == 708;
|
|
} catch (exception) {}
|
|
|
|
// Internal: Determines whether the native `JSON.stringify` and `parse`
|
|
// implementations are spec-compliant. Based on work by Ken Snyder.
|
|
function has(name) {
|
|
var stringifySupported, parseSupported, value, serialized = '{"A":[1,true,false,null,"\\u0000\\b\\n\\f\\r\\t"]}', all = name == "json";
|
|
if (all || name == "json-stringify" || name == "json-parse") {
|
|
// Test `JSON.stringify`.
|
|
if (name == "json-stringify" || all) {
|
|
if ((stringifySupported = typeof JSON3.stringify == "function" && isExtended)) {
|
|
// A test function object with a custom `toJSON` method.
|
|
(value = function () {
|
|
return 1;
|
|
}).toJSON = value;
|
|
try {
|
|
stringifySupported =
|
|
// Firefox 3.1b1 and b2 serialize string, number, and boolean
|
|
// primitives as object literals.
|
|
JSON3.stringify(0) === "0" &&
|
|
// FF 3.1b1, b2, and JSON 2 serialize wrapped primitives as object
|
|
// literals.
|
|
JSON3.stringify(new Number()) === "0" &&
|
|
JSON3.stringify(new String()) == '""' &&
|
|
// FF 3.1b1, 2 throw an error if the value is `null`, `undefined`, or
|
|
// does not define a canonical JSON representation (this applies to
|
|
// objects with `toJSON` properties as well, *unless* they are nested
|
|
// within an object or array).
|
|
JSON3.stringify(getClass) === undef &&
|
|
// IE 8 serializes `undefined` as `"undefined"`. Safari <= 5.1.7 and
|
|
// FF 3.1b3 pass this test.
|
|
JSON3.stringify(undef) === undef &&
|
|
// Safari <= 5.1.7 and FF 3.1b3 throw `Error`s and `TypeError`s,
|
|
// respectively, if the value is omitted entirely.
|
|
JSON3.stringify() === undef &&
|
|
// FF 3.1b1, 2 throw an error if the given value is not a number,
|
|
// string, array, object, Boolean, or `null` literal. This applies to
|
|
// objects with custom `toJSON` methods as well, unless they are nested
|
|
// inside object or array literals. YUI 3.0.0b1 ignores custom `toJSON`
|
|
// methods entirely.
|
|
JSON3.stringify(value) === "1" &&
|
|
JSON3.stringify([value]) == "[1]" &&
|
|
// Prototype <= 1.6.1 serializes `[undefined]` as `"[]"` instead of
|
|
// `"[null]"`.
|
|
JSON3.stringify([undef]) == "[null]" &&
|
|
// YUI 3.0.0b1 fails to serialize `null` literals.
|
|
JSON3.stringify(null) == "null" &&
|
|
// FF 3.1b1, 2 halts serialization if an array contains a function:
|
|
// `[1, true, getClass, 1]` serializes as "[1,true,],". These versions
|
|
// of Firefox also allow trailing commas in JSON objects and arrays.
|
|
// FF 3.1b3 elides non-JSON values from objects and arrays, unless they
|
|
// define custom `toJSON` methods.
|
|
JSON3.stringify([undef, getClass, null]) == "[null,null,null]" &&
|
|
// Simple serialization test. FF 3.1b1 uses Unicode escape sequences
|
|
// where character escape codes are expected (e.g., `\b` => `\u0008`).
|
|
JSON3.stringify({ "A": [value, true, false, null, "\0\b\n\f\r\t"] }) == serialized &&
|
|
// FF 3.1b1 and b2 ignore the `filter` and `width` arguments.
|
|
JSON3.stringify(null, value) === "1" &&
|
|
JSON3.stringify([1, 2], null, 1) == "[\n 1,\n 2\n]" &&
|
|
// JSON 2, Prototype <= 1.7, and older WebKit builds incorrectly
|
|
// serialize extended years.
|
|
JSON3.stringify(new Date(-8.64e15)) == '"-271821-04-20T00:00:00.000Z"' &&
|
|
// The milliseconds are optional in ES 5, but required in 5.1.
|
|
JSON3.stringify(new Date(8.64e15)) == '"+275760-09-13T00:00:00.000Z"' &&
|
|
// Firefox <= 11.0 incorrectly serializes years prior to 0 as negative
|
|
// four-digit years instead of six-digit years. Credits: @Yaffle.
|
|
JSON3.stringify(new Date(-621987552e5)) == '"-000001-01-01T00:00:00.000Z"' &&
|
|
// Safari <= 5.1.5 and Opera >= 10.53 incorrectly serialize millisecond
|
|
// values less than 1000. Credits: @Yaffle.
|
|
JSON3.stringify(new Date(-1)) == '"1969-12-31T23:59:59.999Z"';
|
|
} catch (exception) {
|
|
stringifySupported = false;
|
|
}
|
|
}
|
|
if (!all) {
|
|
return stringifySupported;
|
|
}
|
|
}
|
|
// Test `JSON.parse`.
|
|
if (name == "json-parse" || all) {
|
|
if (typeof JSON3.parse == "function") {
|
|
try {
|
|
// FF 3.1b1, b2 will throw an exception if a bare literal is provided.
|
|
// Conforming implementations should also coerce the initial argument to
|
|
// a string prior to parsing.
|
|
if (JSON3.parse("0") === 0 && !JSON3.parse(false)) {
|
|
// Simple parsing test.
|
|
value = JSON3.parse(serialized);
|
|
if ((parseSupported = value.A.length == 5 && value.A[0] == 1)) {
|
|
try {
|
|
// Safari <= 5.1.2 and FF 3.1b1 allow unescaped tabs in strings.
|
|
parseSupported = !JSON3.parse('"\t"');
|
|
} catch (exception) {}
|
|
if (parseSupported) {
|
|
try {
|
|
// FF 4.0 and 4.0.1 allow leading `+` signs, and leading and
|
|
// trailing decimal points. FF 4.0, 4.0.1, and IE 9-10 also
|
|
// allow certain octal literals.
|
|
parseSupported = JSON3.parse("01") != 1;
|
|
} catch (exception) {}
|
|
}
|
|
}
|
|
}
|
|
} catch (exception) {
|
|
parseSupported = false;
|
|
}
|
|
}
|
|
if (!all) {
|
|
return parseSupported;
|
|
}
|
|
}
|
|
return stringifySupported && parseSupported;
|
|
}
|
|
}
|
|
|
|
if (!has("json")) {
|
|
// Define additional utility methods if the `Date` methods are buggy.
|
|
if (!isExtended) {
|
|
floor = Math.floor;
|
|
// A mapping between the months of the year and the number of days between
|
|
// January 1st and the first of the respective month.
|
|
Months = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
|
|
// Internal: Calculates the number of days between the Unix epoch and the
|
|
// first day of the given month.
|
|
getDay = function (year, month) {
|
|
return Months[month] + 365 * (year - 1970) + floor((year - 1969 + (month = +(month > 1))) / 4) - floor((year - 1901 + month) / 100) + floor((year - 1601 + month) / 400);
|
|
};
|
|
}
|
|
|
|
// Internal: Determines if a property is a direct property of the given
|
|
// object. Delegates to the native `Object#hasOwnProperty` method.
|
|
if (!(isProperty = {}.hasOwnProperty)) {
|
|
isProperty = function (property) {
|
|
var members = {}, constructor;
|
|
if ((members.__proto__ = null, members.__proto__ = {
|
|
// The *proto* property cannot be set multiple times in recent
|
|
// versions of Firefox and SeaMonkey.
|
|
"toString": 1
|
|
}, members).toString != getClass) {
|
|
// Safari <= 2.0.3 doesn't implement `Object#hasOwnProperty`, but
|
|
// supports the mutable *proto* property.
|
|
isProperty = function (property) {
|
|
// Capture and break the object's prototype chain (see section 8.6.2
|
|
// of the ES 5.1 spec). The parenthesized expression prevents an
|
|
// unsafe transformation by the Closure Compiler.
|
|
var original = this.__proto__, result = property in (this.__proto__ = null, this);
|
|
// Restore the original prototype chain.
|
|
this.__proto__ = original;
|
|
return result;
|
|
};
|
|
} else {
|
|
// Capture a reference to the top-level `Object` constructor.
|
|
constructor = members.constructor;
|
|
// Use the `constructor` property to simulate `Object#hasOwnProperty` in
|
|
// other environments.
|
|
isProperty = function (property) {
|
|
var parent = (this.constructor || constructor).prototype;
|
|
return property in this && !(property in parent && this[property] === parent[property]);
|
|
};
|
|
}
|
|
members = null;
|
|
return isProperty.call(this, property);
|
|
};
|
|
}
|
|
|
|
// Internal: Normalizes the `for...in` iteration algorithm across
|
|
// environments. Each enumerated key is yielded to a `callback` function.
|
|
forEach = function (object, callback) {
|
|
var size = 0, Properties, members, property, forEach;
|
|
|
|
// Tests for bugs in the current environment's `for...in` algorithm. The
|
|
// `valueOf` property inherits the non-enumerable flag from
|
|
// `Object.prototype` in older versions of IE, Netscape, and Mozilla.
|
|
(Properties = function () {
|
|
this.valueOf = 0;
|
|
}).prototype.valueOf = 0;
|
|
|
|
// Iterate over a new instance of the `Properties` class.
|
|
members = new Properties();
|
|
for (property in members) {
|
|
// Ignore all properties inherited from `Object.prototype`.
|
|
if (isProperty.call(members, property)) {
|
|
size++;
|
|
}
|
|
}
|
|
Properties = members = null;
|
|
|
|
// Normalize the iteration algorithm.
|
|
if (!size) {
|
|
// A list of non-enumerable properties inherited from `Object.prototype`.
|
|
members = ["valueOf", "toString", "toLocaleString", "propertyIsEnumerable", "isPrototypeOf", "hasOwnProperty", "constructor"];
|
|
// IE <= 8, Mozilla 1.0, and Netscape 6.2 ignore shadowed non-enumerable
|
|
// properties.
|
|
forEach = function (object, callback) {
|
|
var isFunction = getClass.call(object) == "[object Function]", property, length;
|
|
for (property in object) {
|
|
// Gecko <= 1.0 enumerates the `prototype` property of functions under
|
|
// certain conditions; IE does not.
|
|
if (!(isFunction && property == "prototype") && isProperty.call(object, property)) {
|
|
callback(property);
|
|
}
|
|
}
|
|
// Manually invoke the callback for each non-enumerable property.
|
|
for (length = members.length; property = members[--length]; isProperty.call(object, property) && callback(property));
|
|
};
|
|
} else if (size == 2) {
|
|
// Safari <= 2.0.4 enumerates shadowed properties twice.
|
|
forEach = function (object, callback) {
|
|
// Create a set of iterated properties.
|
|
var members = {}, isFunction = getClass.call(object) == "[object Function]", property;
|
|
for (property in object) {
|
|
// Store each property name to prevent double enumeration. The
|
|
// `prototype` property of functions is not enumerated due to cross-
|
|
// environment inconsistencies.
|
|
if (!(isFunction && property == "prototype") && !isProperty.call(members, property) && (members[property] = 1) && isProperty.call(object, property)) {
|
|
callback(property);
|
|
}
|
|
}
|
|
};
|
|
} else {
|
|
// No bugs detected; use the standard `for...in` algorithm.
|
|
forEach = function (object, callback) {
|
|
var isFunction = getClass.call(object) == "[object Function]", property, isConstructor;
|
|
for (property in object) {
|
|
if (!(isFunction && property == "prototype") && isProperty.call(object, property) && !(isConstructor = property === "constructor")) {
|
|
callback(property);
|
|
}
|
|
}
|
|
// Manually invoke the callback for the `constructor` property due to
|
|
// cross-environment inconsistencies.
|
|
if (isConstructor || isProperty.call(object, (property = "constructor"))) {
|
|
callback(property);
|
|
}
|
|
};
|
|
}
|
|
return forEach(object, callback);
|
|
};
|
|
|
|
// Public: Serializes a JavaScript `value` as a JSON string. The optional
|
|
// `filter` argument may specify either a function that alters how object and
|
|
// array members are serialized, or an array of strings and numbers that
|
|
// indicates which properties should be serialized. The optional `width`
|
|
// argument may be either a string or number that specifies the indentation
|
|
// level of the output.
|
|
if (!has("json-stringify")) {
|
|
// Internal: A map of control characters and their escaped equivalents.
|
|
Escapes = {
|
|
"\\": "\\\\",
|
|
'"': '\\"',
|
|
"\b": "\\b",
|
|
"\f": "\\f",
|
|
"\n": "\\n",
|
|
"\r": "\\r",
|
|
"\t": "\\t"
|
|
};
|
|
|
|
// Internal: Converts `value` into a zero-padded string such that its
|
|
// length is at least equal to `width`. The `width` must be <= 6.
|
|
toPaddedString = function (width, value) {
|
|
// The `|| 0` expression is necessary to work around a bug in
|
|
// Opera <= 7.54u2 where `0 == -0`, but `String(-0) !== "0"`.
|
|
return ("000000" + (value || 0)).slice(-width);
|
|
};
|
|
|
|
// Internal: Double-quotes a string `value`, replacing all ASCII control
|
|
// characters (characters with code unit values between 0 and 31) with
|
|
// their escaped equivalents. This is an implementation of the
|
|
// `Quote(value)` operation defined in ES 5.1 section 15.12.3.
|
|
quote = function (value) {
|
|
var result = '"', index = 0, symbol;
|
|
for (; symbol = value.charAt(index); index++) {
|
|
// Escape the reverse solidus, double quote, backspace, form feed, line
|
|
// feed, carriage return, and tab characters.
|
|
result += '\\"\b\f\n\r\t'.indexOf(symbol) > -1 ? Escapes[symbol] :
|
|
// If the character is a control character, append its Unicode escape
|
|
// sequence; otherwise, append the character as-is.
|
|
(Escapes[symbol] = symbol < " " ? "\\u00" + toPaddedString(2, symbol.charCodeAt(0).toString(16)) : symbol);
|
|
}
|
|
return result + '"';
|
|
};
|
|
|
|
// Internal: Recursively serializes an object. Implements the
|
|
// `Str(key, holder)`, `JO(value)`, and `JA(value)` operations.
|
|
serialize = function (property, object, callback, properties, whitespace, indentation, stack) {
|
|
var value = object[property], className, year, month, date, time, hours, minutes, seconds, milliseconds, results, element, index, length, prefix, any, result;
|
|
if (typeof value == "object" && value) {
|
|
className = getClass.call(value);
|
|
if (className == "[object Date]" && !isProperty.call(value, "toJSON")) {
|
|
if (value > -1 / 0 && value < 1 / 0) {
|
|
// Dates are serialized according to the `Date#toJSON` method
|
|
// specified in ES 5.1 section 15.9.5.44. See section 15.9.1.15
|
|
// for the ISO 8601 date time string format.
|
|
if (getDay) {
|
|
// Manually compute the year, month, date, hours, minutes,
|
|
// seconds, and milliseconds if the `getUTC*` methods are
|
|
// buggy. Adapted from @Yaffle's `date-shim` project.
|
|
date = floor(value / 864e5);
|
|
for (year = floor(date / 365.2425) + 1970 - 1; getDay(year + 1, 0) <= date; year++);
|
|
for (month = floor((date - getDay(year, 0)) / 30.42); getDay(year, month + 1) <= date; month++);
|
|
date = 1 + date - getDay(year, month);
|
|
// The `time` value specifies the time within the day (see ES
|
|
// 5.1 section 15.9.1.2). The formula `(A % B + B) % B` is used
|
|
// to compute `A modulo B`, as the `%` operator does not
|
|
// correspond to the `modulo` operation for negative numbers.
|
|
time = (value % 864e5 + 864e5) % 864e5;
|
|
// The hours, minutes, seconds, and milliseconds are obtained by
|
|
// decomposing the time within the day. See section 15.9.1.10.
|
|
hours = floor(time / 36e5) % 24;
|
|
minutes = floor(time / 6e4) % 60;
|
|
seconds = floor(time / 1e3) % 60;
|
|
milliseconds = time % 1e3;
|
|
} else {
|
|
year = value.getUTCFullYear();
|
|
month = value.getUTCMonth();
|
|
date = value.getUTCDate();
|
|
hours = value.getUTCHours();
|
|
minutes = value.getUTCMinutes();
|
|
seconds = value.getUTCSeconds();
|
|
milliseconds = value.getUTCMilliseconds();
|
|
}
|
|
// Serialize extended years correctly.
|
|
value = (year <= 0 || year >= 1e4 ? (year < 0 ? "-" : "+") + toPaddedString(6, year < 0 ? -year : year) : toPaddedString(4, year)) +
|
|
"-" + toPaddedString(2, month + 1) + "-" + toPaddedString(2, date) +
|
|
// Months, dates, hours, minutes, and seconds should have two
|
|
// digits; milliseconds should have three.
|
|
"T" + toPaddedString(2, hours) + ":" + toPaddedString(2, minutes) + ":" + toPaddedString(2, seconds) +
|
|
// Milliseconds are optional in ES 5.0, but required in 5.1.
|
|
"." + toPaddedString(3, milliseconds) + "Z";
|
|
} else {
|
|
value = null;
|
|
}
|
|
} else if (typeof value.toJSON == "function" && ((className != "[object Number]" && className != "[object String]" && className != "[object Array]") || isProperty.call(value, "toJSON"))) {
|
|
// Prototype <= 1.6.1 adds non-standard `toJSON` methods to the
|
|
// `Number`, `String`, `Date`, and `Array` prototypes. JSON 3
|
|
// ignores all `toJSON` methods on these objects unless they are
|
|
// defined directly on an instance.
|
|
value = value.toJSON(property);
|
|
}
|
|
}
|
|
if (callback) {
|
|
// If a replacement function was provided, call it to obtain the value
|
|
// for serialization.
|
|
value = callback.call(object, property, value);
|
|
}
|
|
if (value === null) {
|
|
return "null";
|
|
}
|
|
className = getClass.call(value);
|
|
if (className == "[object Boolean]") {
|
|
// Booleans are represented literally.
|
|
return "" + value;
|
|
} else if (className == "[object Number]") {
|
|
// JSON numbers must be finite. `Infinity` and `NaN` are serialized as
|
|
// `"null"`.
|
|
return value > -1 / 0 && value < 1 / 0 ? "" + value : "null";
|
|
} else if (className == "[object String]") {
|
|
// Strings are double-quoted and escaped.
|
|
return quote(value);
|
|
}
|
|
// Recursively serialize objects and arrays.
|
|
if (typeof value == "object") {
|
|
// Check for cyclic structures. This is a linear search; performance
|
|
// is inversely proportional to the number of unique nested objects.
|
|
for (length = stack.length; length--;) {
|
|
if (stack[length] === value) {
|
|
// Cyclic structures cannot be serialized by `JSON.stringify`.
|
|
throw TypeError();
|
|
}
|
|
}
|
|
// Add the object to the stack of traversed objects.
|
|
stack.push(value);
|
|
results = [];
|
|
// Save the current indentation level and indent one additional level.
|
|
prefix = indentation;
|
|
indentation += whitespace;
|
|
if (className == "[object Array]") {
|
|
// Recursively serialize array elements.
|
|
for (index = 0, length = value.length; index < length; any || (any = true), index++) {
|
|
element = serialize(index, value, callback, properties, whitespace, indentation, stack);
|
|
results.push(element === undef ? "null" : element);
|
|
}
|
|
result = any ? (whitespace ? "[\n" + indentation + results.join(",\n" + indentation) + "\n" + prefix + "]" : ("[" + results.join(",") + "]")) : "[]";
|
|
} else {
|
|
// Recursively serialize object members. Members are selected from
|
|
// either a user-specified list of property names, or the object
|
|
// itself.
|
|
forEach(properties || value, function (property) {
|
|
var element = serialize(property, value, callback, properties, whitespace, indentation, stack);
|
|
if (element !== undef) {
|
|
// According to ES 5.1 section 15.12.3: "If `gap` {whitespace}
|
|
// is not the empty string, let `member` {quote(property) + ":"}
|
|
// be the concatenation of `member` and the `space` character."
|
|
// The "`space` character" refers to the literal space
|
|
// character, not the `space` {width} argument provided to
|
|
// `JSON.stringify`.
|
|
results.push(quote(property) + ":" + (whitespace ? " " : "") + element);
|
|
}
|
|
any || (any = true);
|
|
});
|
|
result = any ? (whitespace ? "{\n" + indentation + results.join(",\n" + indentation) + "\n" + prefix + "}" : ("{" + results.join(",") + "}")) : "{}";
|
|
}
|
|
// Remove the object from the traversed object stack.
|
|
stack.pop();
|
|
return result;
|
|
}
|
|
};
|
|
|
|
// Public: `JSON.stringify`. See ES 5.1 section 15.12.3.
|
|
JSON3.stringify = function (source, filter, width) {
|
|
var whitespace, callback, properties, index, length, value;
|
|
if (typeof filter == "function" || typeof filter == "object" && filter) {
|
|
if (getClass.call(filter) == "[object Function]") {
|
|
callback = filter;
|
|
} else if (getClass.call(filter) == "[object Array]") {
|
|
// Convert the property names array into a makeshift set.
|
|
properties = {};
|
|
for (index = 0, length = filter.length; index < length; value = filter[index++], ((getClass.call(value) == "[object String]" || getClass.call(value) == "[object Number]") && (properties[value] = 1)));
|
|
}
|
|
}
|
|
if (width) {
|
|
if (getClass.call(width) == "[object Number]") {
|
|
// Convert the `width` to an integer and create a string containing
|
|
// `width` number of space characters.
|
|
if ((width -= width % 1) > 0) {
|
|
for (whitespace = "", width > 10 && (width = 10); whitespace.length < width; whitespace += " ");
|
|
}
|
|
} else if (getClass.call(width) == "[object String]") {
|
|
whitespace = width.length <= 10 ? width : width.slice(0, 10);
|
|
}
|
|
}
|
|
// Opera <= 7.54u2 discards the values associated with empty string keys
|
|
// (`""`) only if they are used directly within an object member list
|
|
// (e.g., `!("" in { "": 1})`).
|
|
return serialize("", (value = {}, value[""] = source, value), callback, properties, whitespace, "", []);
|
|
};
|
|
}
|
|
|
|
// Public: Parses a JSON source string.
|
|
if (!has("json-parse")) {
|
|
fromCharCode = String.fromCharCode;
|
|
// Internal: A map of escaped control characters and their unescaped
|
|
// equivalents.
|
|
Unescapes = {
|
|
"\\": "\\",
|
|
'"': '"',
|
|
"/": "/",
|
|
"b": "\b",
|
|
"t": "\t",
|
|
"n": "\n",
|
|
"f": "\f",
|
|
"r": "\r"
|
|
};
|
|
|
|
// Internal: Resets the parser state and throws a `SyntaxError`.
|
|
abort = function() {
|
|
Index = Source = null;
|
|
throw SyntaxError();
|
|
};
|
|
|
|
// Internal: Returns the next token, or `"$"` if the parser has reached
|
|
// the end of the source string. A token may be a string, number, `null`
|
|
// literal, or Boolean literal.
|
|
lex = function () {
|
|
var source = Source, length = source.length, symbol, value, begin, position, sign;
|
|
while (Index < length) {
|
|
symbol = source.charAt(Index);
|
|
if ("\t\r\n ".indexOf(symbol) > -1) {
|
|
// Skip whitespace tokens, including tabs, carriage returns, line
|
|
// feeds, and space characters.
|
|
Index++;
|
|
} else if ("{}[]:,".indexOf(symbol) > -1) {
|
|
// Parse a punctuator token at the current position.
|
|
Index++;
|
|
return symbol;
|
|
} else if (symbol == '"') {
|
|
// Advance to the next character and parse a JSON string at the
|
|
// current position. String tokens are prefixed with the sentinel
|
|
// `@` character to distinguish them from punctuators.
|
|
for (value = "@", Index++; Index < length;) {
|
|
symbol = source.charAt(Index);
|
|
if (symbol < " ") {
|
|
// Unescaped ASCII control characters are not permitted.
|
|
abort();
|
|
} else if (symbol == "\\") {
|
|
// Parse escaped JSON control characters, `"`, `\`, `/`, and
|
|
// Unicode escape sequences.
|
|
symbol = source.charAt(++Index);
|
|
if ('\\"/btnfr'.indexOf(symbol) > -1) {
|
|
// Revive escaped control characters.
|
|
value += Unescapes[symbol];
|
|
Index++;
|
|
} else if (symbol == "u") {
|
|
// Advance to the first character of the escape sequence.
|
|
begin = ++Index;
|
|
// Validate the Unicode escape sequence.
|
|
for (position = Index + 4; Index < position; Index++) {
|
|
symbol = source.charAt(Index);
|
|
// A valid sequence comprises four hexdigits that form a
|
|
// single hexadecimal value.
|
|
if (!(symbol >= "0" && symbol <= "9" || symbol >= "a" && symbol <= "f" || symbol >= "A" && symbol <= "F")) {
|
|
// Invalid Unicode escape sequence.
|
|
abort();
|
|
}
|
|
}
|
|
// Revive the escaped character.
|
|
value += fromCharCode("0x" + source.slice(begin, Index));
|
|
} else {
|
|
// Invalid escape sequence.
|
|
abort();
|
|
}
|
|
} else {
|
|
if (symbol == '"') {
|
|
// An unescaped double-quote character marks the end of the
|
|
// string.
|
|
break;
|
|
}
|
|
// Append the original character as-is.
|
|
value += symbol;
|
|
Index++;
|
|
}
|
|
}
|
|
if (source.charAt(Index) == '"') {
|
|
Index++;
|
|
// Return the revived string.
|
|
return value;
|
|
}
|
|
// Unterminated string.
|
|
abort();
|
|
} else {
|
|
// Parse numbers and literals.
|
|
begin = Index;
|
|
// Advance the scanner's position past the sign, if one is
|
|
// specified.
|
|
if (symbol == "-") {
|
|
sign = true;
|
|
symbol = source.charAt(++Index);
|
|
}
|
|
// Parse an integer or floating-point value.
|
|
if (symbol >= "0" && symbol <= "9") {
|
|
// Leading zeroes are interpreted as octal literals.
|
|
if (symbol == "0" && (symbol = source.charAt(Index + 1), symbol >= "0" && symbol <= "9")) {
|
|
// Illegal octal literal.
|
|
abort();
|
|
}
|
|
sign = false;
|
|
// Parse the integer component.
|
|
for (; Index < length && (symbol = source.charAt(Index), symbol >= "0" && symbol <= "9"); Index++);
|
|
// Floats cannot contain a leading decimal point; however, this
|
|
// case is already accounted for by the parser.
|
|
if (source.charAt(Index) == ".") {
|
|
position = ++Index;
|
|
// Parse the decimal component.
|
|
for (; position < length && (symbol = source.charAt(position), symbol >= "0" && symbol <= "9"); position++);
|
|
if (position == Index) {
|
|
// Illegal trailing decimal.
|
|
abort();
|
|
}
|
|
Index = position;
|
|
}
|
|
// Parse exponents.
|
|
symbol = source.charAt(Index);
|
|
if (symbol == "e" || symbol == "E") {
|
|
// Skip past the sign following the exponent, if one is
|
|
// specified.
|
|
symbol = source.charAt(++Index);
|
|
if (symbol == "+" || symbol == "-") {
|
|
Index++;
|
|
}
|
|
// Parse the exponential component.
|
|
for (position = Index; position < length && (symbol = source.charAt(position), symbol >= "0" && symbol <= "9"); position++);
|
|
if (position == Index) {
|
|
// Illegal empty exponent.
|
|
abort();
|
|
}
|
|
Index = position;
|
|
}
|
|
// Coerce the parsed value to a JavaScript number.
|
|
return +source.slice(begin, Index);
|
|
}
|
|
// A negative sign may only precede numbers.
|
|
if (sign) {
|
|
abort();
|
|
}
|
|
// `true`, `false`, and `null` literals.
|
|
if (source.slice(Index, Index + 4) == "true") {
|
|
Index += 4;
|
|
return true;
|
|
} else if (source.slice(Index, Index + 5) == "false") {
|
|
Index += 5;
|
|
return false;
|
|
} else if (source.slice(Index, Index + 4) == "null") {
|
|
Index += 4;
|
|
return null;
|
|
}
|
|
// Unrecognized token.
|
|
abort();
|
|
}
|
|
}
|
|
// Return the sentinel `$` character if the parser has reached the end
|
|
// of the source string.
|
|
return "$";
|
|
};
|
|
|
|
// Internal: Parses a JSON `value` token.
|
|
get = function (value) {
|
|
var results, any, key;
|
|
if (value == "$") {
|
|
// Unexpected end of input.
|
|
abort();
|
|
}
|
|
if (typeof value == "string") {
|
|
if (value.charAt(0) == "@") {
|
|
// Remove the sentinel `@` character.
|
|
return value.slice(1);
|
|
}
|
|
// Parse object and array literals.
|
|
if (value == "[") {
|
|
// Parses a JSON array, returning a new JavaScript array.
|
|
results = [];
|
|
for (;; any || (any = true)) {
|
|
value = lex();
|
|
// A closing square bracket marks the end of the array literal.
|
|
if (value == "]") {
|
|
break;
|
|
}
|
|
// If the array literal contains elements, the current token
|
|
// should be a comma separating the previous element from the
|
|
// next.
|
|
if (any) {
|
|
if (value == ",") {
|
|
value = lex();
|
|
if (value == "]") {
|
|
// Unexpected trailing `,` in array literal.
|
|
abort();
|
|
}
|
|
} else {
|
|
// A `,` must separate each array element.
|
|
abort();
|
|
}
|
|
}
|
|
// Elisions and leading commas are not permitted.
|
|
if (value == ",") {
|
|
abort();
|
|
}
|
|
results.push(get(value));
|
|
}
|
|
return results;
|
|
} else if (value == "{") {
|
|
// Parses a JSON object, returning a new JavaScript object.
|
|
results = {};
|
|
for (;; any || (any = true)) {
|
|
value = lex();
|
|
// A closing curly brace marks the end of the object literal.
|
|
if (value == "}") {
|
|
break;
|
|
}
|
|
// If the object literal contains members, the current token
|
|
// should be a comma separator.
|
|
if (any) {
|
|
if (value == ",") {
|
|
value = lex();
|
|
if (value == "}") {
|
|
// Unexpected trailing `,` in object literal.
|
|
abort();
|
|
}
|
|
} else {
|
|
// A `,` must separate each object member.
|
|
abort();
|
|
}
|
|
}
|
|
// Leading commas are not permitted, object property names must be
|
|
// double-quoted strings, and a `:` must separate each property
|
|
// name and value.
|
|
if (value == "," || typeof value != "string" || value.charAt(0) != "@" || lex() != ":") {
|
|
abort();
|
|
}
|
|
results[value.slice(1)] = get(lex());
|
|
}
|
|
return results;
|
|
}
|
|
// Unexpected token encountered.
|
|
abort();
|
|
}
|
|
return value;
|
|
};
|
|
|
|
// Internal: Updates a traversed object member.
|
|
update = function(source, property, callback) {
|
|
var element = walk(source, property, callback);
|
|
if (element === undef) {
|
|
delete source[property];
|
|
} else {
|
|
source[property] = element;
|
|
}
|
|
};
|
|
|
|
// Internal: Recursively traverses a parsed JSON object, invoking the
|
|
// `callback` function for each value. This is an implementation of the
|
|
// `Walk(holder, name)` operation defined in ES 5.1 section 15.12.2.
|
|
walk = function (source, property, callback) {
|
|
var value = source[property], length;
|
|
if (typeof value == "object" && value) {
|
|
if (getClass.call(value) == "[object Array]") {
|
|
for (length = value.length; length--;) {
|
|
update(value, length, callback);
|
|
}
|
|
} else {
|
|
// `forEach` can't be used to traverse an array in Opera <= 8.54,
|
|
// as `Object#hasOwnProperty` returns `false` for array indices
|
|
// (e.g., `![1, 2, 3].hasOwnProperty("0")`).
|
|
forEach(value, function (property) {
|
|
update(value, property, callback);
|
|
});
|
|
}
|
|
}
|
|
return callback.call(source, property, value);
|
|
};
|
|
|
|
// Public: `JSON.parse`. See ES 5.1 section 15.12.2.
|
|
JSON3.parse = function (source, callback) {
|
|
var result, value;
|
|
Index = 0;
|
|
Source = source;
|
|
result = get(lex());
|
|
// If a JSON string contains multiple tokens, it is invalid.
|
|
if (lex() != "$") {
|
|
abort();
|
|
}
|
|
// Reset the parser state.
|
|
Index = Source = null;
|
|
return callback && getClass.call(callback) == "[object Function]" ? walk((value = {}, value[""] = result, value), "", callback) : result;
|
|
};
|
|
}
|
|
}
|
|
|
|
// Export for asynchronous module loaders.
|
|
if (isLoader) {
|
|
define(function () {
|
|
return JSON3;
|
|
});
|
|
}
|
|
}).call(this); |