From ff55d8d4513b149e2511aee01c3a61d372837d1f Mon Sep 17 00:00:00 2001 From: Douglas Crockford Date: Wed, 20 Feb 2013 03:42:34 -0800 Subject: [PATCH 01/28] no mess --- cycle.js | 28 ++++++++++------------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/cycle.js b/cycle.js index 7187bf4..5312e38 100644 --- a/cycle.js +++ b/cycle.js @@ -1,6 +1,6 @@ /* cycle.js - 2012-08-19 + 2013-02-19 Public Domain. @@ -50,20 +50,15 @@ if (typeof JSON.decycle !== 'function') { name, // Property name nu; // The new object or array - switch (typeof value) { - case 'object': +// typeof null === 'object', so go on if this value is really an object but not +// one of the weird builtin objects. -// typeof null === 'object', so get out if this value is not really an object. -// Also get out if it is a weird builtin object. - - if (value === null || - value instanceof Boolean || - value instanceof Date || - value instanceof Number || - value instanceof RegExp || - value instanceof String) { - return value; - } + if (typeof value === 'object' && value !== null && + !(value instanceof Boolean) && + !(value instanceof Date) && + !(value instanceof Number) && + !(value instanceof RegExp) && + !(value instanceof String)) { // If the value is an object or array, look to see if we have already // encountered it. If so, return a $ref/path object. This is a hard way, @@ -100,11 +95,8 @@ if (typeof JSON.decycle !== 'function') { } } return nu; - case 'number': - case 'string': - case 'boolean': - return value; } + return value; }(object, '$')); }; } From e39db4b7e6249f04a195e7dd0840e610cc9e941e Mon Sep 17 00:00:00 2001 From: Douglas Crockford Date: Sun, 26 May 2013 07:58:04 -0700 Subject: [PATCH 02/28] hygiene --- json.js | 42 ++++++++++++++++++++++++------------------ json2.js | 6 +++--- json_parse_state.js | 4 ++-- 3 files changed, 29 insertions(+), 23 deletions(-) diff --git a/json.js b/json.js index 9a338bf..f673229 100644 --- a/json.js +++ b/json.js @@ -1,6 +1,6 @@ /* json.js - 2012-10-08 + 2013-05-26 Public Domain @@ -212,13 +212,14 @@ if (typeof JSON !== 'object') { Date.prototype.toJSON = function (key) { - return isFinite(this.valueOf()) ? - this.getUTCFullYear() + '-' + - f(this.getUTCMonth() + 1) + '-' + - f(this.getUTCDate()) + 'T' + - f(this.getUTCHours()) + ':' + - f(this.getUTCMinutes()) + ':' + - f(this.getUTCSeconds()) + 'Z' : null; + return isFinite(this.valueOf()) + ? this.getUTCFullYear() + '-' + + f(this.getUTCMonth() + 1) + '-' + + f(this.getUTCDate()) + 'T' + + f(this.getUTCHours()) + ':' + + f(this.getUTCMinutes()) + ':' + + f(this.getUTCSeconds()) + 'Z' + : null; }; String.prototype.toJSON = @@ -254,8 +255,9 @@ if (typeof JSON !== 'object') { escapable.lastIndex = 0; return escapable.test(string) ? '"' + string.replace(escapable, function (a) { var c = meta[a]; - return typeof c === 'string' ? c : - '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + return typeof c === 'string' + ? c + : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); }) + '"' : '"' + string + '"'; } @@ -339,9 +341,11 @@ if (typeof JSON !== 'object') { // Join all of the elements together, separated with commas, and wrap them in // brackets. - v = partial.length === 0 ? '[]' : gap ? - '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' : - '[' + partial.join(',') + ']'; + v = partial.length === 0 + ? '[]' + : gap + ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' + : '[' + partial.join(',') + ']'; gap = mind; return v; } @@ -376,9 +380,10 @@ if (typeof JSON !== 'object') { // Join all of the member texts together, separated with commas, // and wrap them in braces. - v = partial.length === 0 ? '{}' : gap ? - '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' : - '{' + partial.join(',') + '}'; + v = partial.length === 0 ? '{}' + : gap + ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' + : '{' + partial.join(',') + '}'; gap = mind; return v; } @@ -504,8 +509,9 @@ if (typeof JSON !== 'object') { // In the optional fourth stage, we recursively walk the new structure, passing // each name/value pair to a reviver function for possible transformation. - return typeof reviver === 'function' ? - walk({'': j}, '') : j; + return typeof reviver === 'function' + ? walk({'': j}, '') + : j; } // If the text is not JSON parseable, then a SyntaxError is thrown. diff --git a/json2.js b/json2.js index c7745df..d89ecc7 100644 --- a/json2.js +++ b/json2.js @@ -1,6 +1,6 @@ /* json2.js - 2012-10-08 + 2013-05-26 Public Domain. @@ -173,7 +173,7 @@ if (typeof JSON !== 'object') { if (typeof Date.prototype.toJSON !== 'function') { - Date.prototype.toJSON = function (key) { + Date.prototype.toJSON = function () { return isFinite(this.valueOf()) ? this.getUTCFullYear() + '-' + @@ -187,7 +187,7 @@ if (typeof JSON !== 'object') { String.prototype.toJSON = Number.prototype.toJSON = - Boolean.prototype.toJSON = function (key) { + Boolean.prototype.toJSON = function () { return this.valueOf(); }; } diff --git a/json_parse_state.js b/json_parse_state.js index 142229f..7424fcc 100644 --- a/json_parse_state.js +++ b/json_parse_state.js @@ -1,6 +1,6 @@ /* json_parse_state.js - 2012-06-01 + 2013-05-26 Public Domain. @@ -46,7 +46,7 @@ NOT CONTROL. */ -/*jslint regexp: false*/ +/*jslint regexp: true, unparam: true */ /*members "", "\"", ",", "\/", ":", "[", "\\", "]", acomma, avalue, b, call, colon, container, exec, f, false, firstavalue, firstokey, From 2468916ef05a9324d8e544ff04dd5200728e90a0 Mon Sep 17 00:00:00 2001 From: Douglas Crockford Date: Tue, 4 Feb 2014 12:40:39 -0800 Subject: [PATCH 03/28] IE8 --- json2.js | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/json2.js b/json2.js index d89ecc7..deb88ec 100644 --- a/json2.js +++ b/json2.js @@ -1,6 +1,6 @@ /* json2.js - 2013-05-26 + 2014-02-04 Public Domain. @@ -192,19 +192,11 @@ if (typeof JSON !== 'object') { }; } - var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, - escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, + var cx, + escapable, gap, indent, - meta = { // table of character substitutions - '\b': '\\b', - '\t': '\\t', - '\n': '\\n', - '\f': '\\f', - '\r': '\\r', - '"' : '\\"', - '\\': '\\\\' - }, + meta, rep; @@ -356,6 +348,16 @@ if (typeof JSON !== 'object') { // If the JSON object does not yet have a stringify method, give it one. if (typeof JSON.stringify !== 'function') { + escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; + meta = { // table of character substitutions + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '"' : '\\"', + '\\': '\\\\' + }; JSON.stringify = function (value, replacer, space) { // The stringify method takes a value and an optional replacer, and an optional @@ -403,6 +405,7 @@ if (typeof JSON !== 'object') { // If the JSON object does not yet have a parse method, give it one. if (typeof JSON.parse !== 'function') { + cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; JSON.parse = function (text, reviver) { // The parse method takes a text and an optional reviver function, and returns From 3d7767b6b1f3da363c625ff54e63bbf20e9e83ac Mon Sep 17 00:00:00 2001 From: Douglas Crockford Date: Tue, 4 Feb 2014 12:44:41 -0800 Subject: [PATCH 04/28] IE8 --- json.js | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/json.js b/json.js index f673229..ef41ecd 100644 --- a/json.js +++ b/json.js @@ -1,6 +1,6 @@ /* json.js - 2013-05-26 + 2014-02-04 Public Domain @@ -229,19 +229,11 @@ if (typeof JSON !== 'object') { }; } - var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, - escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, + var cx, + escapable, gap, indent, - meta = { // table of character substitutions - '\b': '\\b', - '\t': '\\t', - '\n': '\\n', - '\f': '\\f', - '\r': '\\r', - '"' : '\\"', - '\\': '\\\\' - }, + meta, rep; @@ -392,6 +384,16 @@ if (typeof JSON !== 'object') { // If the JSON object does not yet have a stringify method, give it one. if (typeof JSON.stringify !== 'function') { + escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; + meta = { // table of character substitutions + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '"' : '\\"', + '\\': '\\\\' + }; JSON.stringify = function (value, replacer, space) { // The stringify method takes a value and an optional replacer, and an optional @@ -439,6 +441,7 @@ if (typeof JSON !== 'object') { // If the JSON object does not yet have a parse method, give it one. if (typeof JSON.parse !== 'function') { + cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; JSON.parse = function (text, reviver) { // The parse method takes a text and an optional reviver function, and returns From 4982ffbecf8175f233b0c9626bc8629c70faf5a2 Mon Sep 17 00:00:00 2001 From: Douglas Crockford Date: Wed, 25 Feb 2015 10:21:21 -0800 Subject: [PATCH 05/28] Anticipating new JSLint --- cycle.js | 18 ++++---- json.js | 97 +++++++++++++++++++++++------------------ json2.js | 103 +++++++++++++++++++++++++++----------------- json_parse.js | 61 ++++++++++++++------------ json_parse_state.js | 36 +++++++++------- 5 files changed, 181 insertions(+), 134 deletions(-) diff --git a/cycle.js b/cycle.js index 5312e38..0839437 100644 --- a/cycle.js +++ b/cycle.js @@ -1,6 +1,6 @@ /* cycle.js - 2013-02-19 + 2015-02-25 Public Domain. @@ -13,9 +13,10 @@ NOT CONTROL. */ -/*jslint evil: true, regexp: true */ +/*jslint eval, for */ -/*members $ref, apply, call, decycle, hasOwnProperty, length, prototype, push, +/*property + $ref, apply, call, decycle, hasOwnProperty, length, prototype, push, retrocycle, stringify, test, toString */ @@ -55,9 +56,9 @@ if (typeof JSON.decycle !== 'function') { if (typeof value === 'object' && value !== null && !(value instanceof Boolean) && - !(value instanceof Date) && - !(value instanceof Number) && - !(value instanceof RegExp) && + !(value instanceof Date) && + !(value instanceof Number) && + !(value instanceof RegExp) && !(value instanceof String)) { // If the value is an object or array, look to see if we have already @@ -90,7 +91,7 @@ if (typeof JSON.decycle !== 'function') { for (name in value) { if (Object.prototype.hasOwnProperty.call(value, name)) { nu[name] = derez(value[name], - path + '[' + JSON.stringify(name) + ']'); + path + '[' + JSON.stringify(name) + ']'); } } } @@ -125,8 +126,7 @@ if (typeof JSON.retrocycle !== 'function') { // return JSON.retrocycle(JSON.parse(s)); // produces an array containing a single element which is the array itself. - var px = - /^\$(?:\[(?:\d+|\"(?:[^\\\"\u0000-\u001f]|\\([\\\"\/bfnrt]|u[0-9a-zA-Z]{4}))*\")\])*$/; + var px = /^\$(?:\[(?:\d+|\"(?:[^\\\"\u0000-\u001f]|\\([\\\"\/bfnrt]|u[0-9a-zA-Z]{4}))*\")\])*$/; (function rez(value) { diff --git a/json.js b/json.js index ef41ecd..8f5d91e 100644 --- a/json.js +++ b/json.js @@ -1,6 +1,6 @@ /* json.js - 2014-02-04 + 2014-02-25 Public Domain @@ -183,16 +183,15 @@ redistribute. */ -/*jslint evil: true, regexp: true, unparam: true */ +/*jslint eval, for, this */ -/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, - call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, +/*property + JSON, apply, call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, lastIndex, length, parse, parseJSON, prototype, push, replace, slice, stringify, test, toJSON, toJSONString, toString, valueOf */ - // Create a JSON object only if one does not already exist. We create the // methods in a closure to avoid creating global variables. @@ -205,28 +204,32 @@ if (typeof JSON !== 'object') { function f(n) { // Format integers to have at least two digits. - return n < 10 ? '0' + n : n; + return n < 10 + ? '0' + n + : n; + } + + function this_value() { + return this.valueOf(); } if (typeof Date.prototype.toJSON !== 'function') { - Date.prototype.toJSON = function (key) { + Date.prototype.toJSON = function (ignore) { return isFinite(this.valueOf()) - ? this.getUTCFullYear() + '-' + + ? this.getUTCFullYear() + '-' + f(this.getUTCMonth() + 1) + '-' + - f(this.getUTCDate()) + 'T' + - f(this.getUTCHours()) + ':' + - f(this.getUTCMinutes()) + ':' + - f(this.getUTCSeconds()) + 'Z' - : null; + f(this.getUTCDate()) + 'T' + + f(this.getUTCHours()) + ':' + + f(this.getUTCMinutes()) + ':' + + f(this.getUTCSeconds()) + 'Z' + : null; }; - String.prototype.toJSON = - Number.prototype.toJSON = - Boolean.prototype.toJSON = function (key) { - return this.valueOf(); - }; + Boolean.prototype.toJSON = this_value; + Number.prototype.toJSON = this_value; + String.prototype.toJSON = this_value; } var cx, @@ -245,12 +248,14 @@ if (typeof JSON !== 'object') { // sequences. escapable.lastIndex = 0; - return escapable.test(string) ? '"' + string.replace(escapable, function (a) { + return escapable.test(string) + ? '"' + string.replace(escapable, function (a) { var c = meta[a]; return typeof c === 'string' - ? c - : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); - }) + '"' : '"' + string + '"'; + ? c + : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }) + '"' + : '"' + string + '"'; } @@ -290,7 +295,9 @@ if (typeof JSON !== 'object') { // JSON numbers must be finite. Encode non-finite numbers as null. - return isFinite(value) ? String(value) : 'null'; + return isFinite(value) + ? String(value) + : 'null'; case 'boolean': case 'null': @@ -334,10 +341,10 @@ if (typeof JSON !== 'object') { // brackets. v = partial.length === 0 - ? '[]' - : gap - ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' - : '[' + partial.join(',') + ']'; + ? '[]' + : gap + ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' + : '[' + partial.join(',') + ']'; gap = mind; return v; } @@ -351,7 +358,9 @@ if (typeof JSON !== 'object') { if (typeof k === 'string') { v = str(k, value); if (v) { - partial.push(quote(k) + (gap ? ': ' : ':') + v); + partial.push(quote(k) + (gap + ? ': ' + : ':') + v); } } } @@ -363,7 +372,9 @@ if (typeof JSON !== 'object') { if (Object.prototype.hasOwnProperty.call(value, k)) { v = str(k, value); if (v) { - partial.push(quote(k) + (gap ? ': ' : ':') + v); + partial.push(quote(k) + (gap + ? ': ' + : ':') + v); } } } @@ -372,10 +383,11 @@ if (typeof JSON !== 'object') { // Join all of the member texts together, separated with commas, // and wrap them in braces. - v = partial.length === 0 ? '{}' - : gap - ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' - : '{' + partial.join(',') + '}'; + v = partial.length === 0 + ? '{}' + : gap + ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' + : '{' + partial.join(',') + '}'; gap = mind; return v; } @@ -384,14 +396,14 @@ if (typeof JSON !== 'object') { // If the JSON object does not yet have a stringify method, give it one. if (typeof JSON.stringify !== 'function') { - escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; + escapable = /[\\\"\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; meta = { // table of character substitutions '\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', - '"' : '\\"', + '"': '\\"', '\\': '\\\\' }; JSON.stringify = function (value, replacer, space) { @@ -480,7 +492,7 @@ if (typeof JSON !== 'object') { if (cx.test(text)) { text = text.replace(cx, function (a) { return '\\u' + - ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); }); } @@ -497,10 +509,11 @@ if (typeof JSON !== 'object') { // we look to see that the remaining characters are only whitespace or ']' or // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. - if (/^[\],:{}\s]*$/ - .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') - .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') - .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { + if (/^[\],:{}\s]*$/.test( + text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') + .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') + .replace(/(?:^|:|,)(?:\s*\[)+/g, '') + )) { // In the third stage we use the eval function to compile the text into a // JavaScript structure. The '{' operator is subject to a syntactic ambiguity @@ -513,8 +526,8 @@ if (typeof JSON !== 'object') { // each name/value pair to a reviver function for possible transformation. return typeof reviver === 'function' - ? walk({'': j}, '') - : j; + ? walk({'': j}, '') + : j; } // If the text is not JSON parseable, then a SyntaxError is thrown. diff --git a/json2.js b/json2.js index deb88ec..d403181 100644 --- a/json2.js +++ b/json2.js @@ -1,6 +1,6 @@ /* json2.js - 2014-02-04 + 2015-02-25 Public Domain. @@ -48,7 +48,9 @@ Date.prototype.toJSON = function (key) { function f(n) { // Format integers to have at least two digits. - return n < 10 ? '0' + n : n; + return n < 10 + ? '0' + n + : n; } return this.getUTCFullYear() + '-' + @@ -146,10 +148,12 @@ redistribute. */ -/*jslint evil: true, regexp: true */ +/*jslint + eval, for, this +*/ -/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, - call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, +/*property + JSON, apply, call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, lastIndex, length, parse, prototype, push, replace, slice, stringify, test, toJSON, toString, valueOf @@ -168,7 +172,13 @@ if (typeof JSON !== 'object') { function f(n) { // Format integers to have at least two digits. - return n < 10 ? '0' + n : n; + return n < 10 + ? '0' + n + : n; + } + + function this_value() { + return this.valueOf(); } if (typeof Date.prototype.toJSON !== 'function') { @@ -176,20 +186,18 @@ if (typeof JSON !== 'object') { Date.prototype.toJSON = function () { return isFinite(this.valueOf()) - ? this.getUTCFullYear() + '-' + + ? this.getUTCFullYear() + '-' + f(this.getUTCMonth() + 1) + '-' + - f(this.getUTCDate()) + 'T' + - f(this.getUTCHours()) + ':' + - f(this.getUTCMinutes()) + ':' + - f(this.getUTCSeconds()) + 'Z' - : null; + f(this.getUTCDate()) + 'T' + + f(this.getUTCHours()) + ':' + + f(this.getUTCMinutes()) + ':' + + f(this.getUTCSeconds()) + 'Z' + : null; }; - String.prototype.toJSON = - Number.prototype.toJSON = - Boolean.prototype.toJSON = function () { - return this.valueOf(); - }; + Boolean.prototype.toJSON = this_value; + Number.prototype.toJSON = this_value; + String.prototype.toJSON = this_value; } var cx, @@ -208,12 +216,14 @@ if (typeof JSON !== 'object') { // sequences. escapable.lastIndex = 0; - return escapable.test(string) ? '"' + string.replace(escapable, function (a) { + return escapable.test(string) + ? '"' + string.replace(escapable, function (a) { var c = meta[a]; return typeof c === 'string' - ? c - : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); - }) + '"' : '"' + string + '"'; + ? c + : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }) + '"' + : '"' + string + '"'; } @@ -253,7 +263,9 @@ if (typeof JSON !== 'object') { // JSON numbers must be finite. Encode non-finite numbers as null. - return isFinite(value) ? String(value) : 'null'; + return isFinite(value) + ? String(value) + : 'null'; case 'boolean': case 'null': @@ -297,10 +309,10 @@ if (typeof JSON !== 'object') { // brackets. v = partial.length === 0 - ? '[]' - : gap - ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' - : '[' + partial.join(',') + ']'; + ? '[]' + : gap + ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' + : '[' + partial.join(',') + ']'; gap = mind; return v; } @@ -314,7 +326,11 @@ if (typeof JSON !== 'object') { k = rep[i]; v = str(k, value); if (v) { - partial.push(quote(k) + (gap ? ': ' : ':') + v); + partial.push(quote(k) + ( + gap + ? ': ' + : ':' + ) + v); } } } @@ -326,7 +342,11 @@ if (typeof JSON !== 'object') { if (Object.prototype.hasOwnProperty.call(value, k)) { v = str(k, value); if (v) { - partial.push(quote(k) + (gap ? ': ' : ':') + v); + partial.push(quote(k) + ( + gap + ? ': ' + : ':' + ) + v); } } } @@ -336,10 +356,10 @@ if (typeof JSON !== 'object') { // and wrap them in braces. v = partial.length === 0 - ? '{}' - : gap - ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' - : '{' + partial.join(',') + '}'; + ? '{}' + : gap + ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' + : '{' + partial.join(',') + '}'; gap = mind; return v; } @@ -348,14 +368,14 @@ if (typeof JSON !== 'object') { // If the JSON object does not yet have a stringify method, give it one. if (typeof JSON.stringify !== 'function') { - escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; + escapable = /[\\\"\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; meta = { // table of character substitutions '\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', - '"' : '\\"', + '"': '\\"', '\\': '\\\\' }; JSON.stringify = function (value, replacer, space) { @@ -444,7 +464,7 @@ if (typeof JSON !== 'object') { if (cx.test(text)) { text = text.replace(cx, function (a) { return '\\u' + - ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); }); } @@ -461,10 +481,13 @@ if (typeof JSON !== 'object') { // we look to see that the remaining characters are only whitespace or ']' or // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. - if (/^[\],:{}\s]*$/ - .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') + if ( + /^[\],:{}\s]*$/.test( + text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') - .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { + .replace(/(?:^|:|,)(?:\s*\[)+/g, '') + ) + ) { // In the third stage we use the eval function to compile the text into a // JavaScript structure. The '{' operator is subject to a syntactic ambiguity @@ -477,8 +500,8 @@ if (typeof JSON !== 'object') { // each name/value pair to a reviver function for possible transformation. return typeof reviver === 'function' - ? walk({'': j}, '') - : j; + ? walk({'': j}, '') + : j; } // If the text is not JSON parseable, then a SyntaxError is thrown. diff --git a/json_parse.js b/json_parse.js index 89b8b77..cd96c3c 100644 --- a/json_parse.js +++ b/json_parse.js @@ -1,6 +1,6 @@ /* json_parse.js - 2012-06-20 + 2015-02-25 Public Domain. @@ -46,8 +46,11 @@ NOT CONTROL. */ -/*members "", "\"", "\/", "\\", at, b, call, charAt, f, fromCharCode, - hasOwnProperty, message, n, name, prototype, push, r, t, text +/*jslint for */ + +/*property + at, b, call, charAt, f, fromCharCode, hasOwnProperty, message, n, name, + prototype, push, r, t, text */ var json_parse = (function () { @@ -64,14 +67,14 @@ var json_parse = (function () { var at, // The index of the current character ch, // The current character escapee = { - '"': '"', + '"': '"', '\\': '\\', - '/': '/', - b: '\b', - f: '\f', - n: '\n', - r: '\r', - t: '\t' + '/': '/', + b: '\b', + f: '\f', + n: '\n', + r: '\r', + t: '\t' }, text, @@ -80,10 +83,10 @@ var json_parse = (function () { // Call error when something is wrong. throw { - name: 'SyntaxError', + name: 'SyntaxError', message: m, - at: at, - text: text + at: at, + text: text }; }, @@ -302,7 +305,9 @@ var json_parse = (function () { case '-': return number(); default: - return ch >= '0' && ch <= '9' ? number() : word(); + return ch >= '0' && ch <= '9' + ? number() + : word(); } }; @@ -328,22 +333,22 @@ var json_parse = (function () { // result. return typeof reviver === 'function' - ? (function walk(holder, key) { - var k, v, value = holder[key]; - if (value && typeof value === 'object') { - for (k in value) { - if (Object.prototype.hasOwnProperty.call(value, k)) { - v = walk(value, k); - if (v !== undefined) { - value[k] = v; - } else { - delete value[k]; - } + ? (function walk(holder, key) { + var k, v, value = holder[key]; + if (value && typeof value === 'object') { + for (k in value) { + if (Object.prototype.hasOwnProperty.call(value, k)) { + v = walk(value, k); + if (v !== undefined) { + value[k] = v; + } else { + delete value[k]; } } } - return reviver.call(holder, key, value); - }({'': result}, '')) - : result; + } + return reviver.call(holder, key, value); + }({'': result}, '')) + : result; }; }()); diff --git a/json_parse_state.js b/json_parse_state.js index 7424fcc..163631d 100644 --- a/json_parse_state.js +++ b/json_parse_state.js @@ -1,6 +1,6 @@ /* json_parse_state.js - 2013-05-26 + 2015-02-25 Public Domain. @@ -46,13 +46,13 @@ NOT CONTROL. */ -/*jslint regexp: true, unparam: true */ +/*jslint for */ -/*members "", "\"", ",", "\/", ":", "[", "\\", "]", acomma, avalue, b, - call, colon, container, exec, f, false, firstavalue, firstokey, - fromCharCode, go, hasOwnProperty, key, length, n, null, ocomma, okey, - ovalue, pop, prototype, push, r, replace, slice, state, t, test, true, - value, "{", "}" +/*property + acomma, avalue, b, call, colon, container, exec, f, false, firstavalue, + firstokey, fromCharCode, go, hasOwnProperty, key, length, n, null, ocomma, + okey, ovalue, pop, prototype, push, r, replace, slice, state, t, test, + true */ var json_parse = (function () { @@ -287,8 +287,10 @@ var json_parse = (function () { // Remove and replace any backslash escapement. - return text.replace(/\\(?:u(.{4})|([^u]))/g, function (a, b, c) { - return b ? String.fromCharCode(parseInt(b, 16)) : escapes[c]; + return text.replace(/\\(?:u(.{4})|([^u]))/g, function (ignore, b, c) { + return b + ? String.fromCharCode(parseInt(b, 16)) + : escapes[c]; }); } @@ -298,7 +300,7 @@ var json_parse = (function () { // The extraction process is cautious. var r, // The result of the exec method. - tx = /^[\x20\t\n\r]*(?:([,:\[\]{}]|true|false|null)|(-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)|"((?:[^\r\n\t\\\"]|\\(?:["\\\/trnfb]|u[0-9a-fA-F]{4}))*)")/; + tx = /^[\u0020\t\n\r]*(?:([,:\[\]{}]|true|false|null)|(-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)|"((?:[^\r\n\t\\\"]|\\(?:["\\\/trnfb]|u[0-9a-fA-F]{4}))*)")/; // Set the starting state. @@ -315,7 +317,7 @@ var json_parse = (function () { // For each token... - for (;;) { + while (true) { r = tx.exec(source); if (!r) { break; @@ -367,8 +369,10 @@ var json_parse = (function () { // remaining source contains anything except whitespace, then we did not have //a well-formed JSON text. - if (state !== 'ok' || /[^\x20\t\n\r]/.test(source)) { - throw state instanceof SyntaxError ? state : new SyntaxError('JSON'); + if (state !== 'ok' || (/[^\u0020\t\n\r]/.test(source))) { + throw state instanceof SyntaxError + ? state + : new SyntaxError('JSON'); } // If there is a reviver function, we recursively walk the new structure, @@ -377,7 +381,8 @@ var json_parse = (function () { // value in an empty key. If there is not a reviver function, we simply return // that value. - return typeof reviver === 'function' ? (function walk(holder, key) { + return typeof reviver === 'function' + ? (function walk(holder, key) { var k, v, value = holder[key]; if (value && typeof value === 'object') { for (k in value) { @@ -392,6 +397,7 @@ var json_parse = (function () { } } return reviver.call(holder, key, value); - }({'': value}, '')) : value; + }({'': value}, '')) + : value; }; }()); From c07c287e39ab5a1726818e0436490bf071b7c838 Mon Sep 17 00:00:00 2001 From: Douglas Crockford Date: Fri, 3 Apr 2015 18:58:13 -0700 Subject: [PATCH 06/28] README --- README | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README b/README index 5f4ad23..3f245fe 100644 --- a/README +++ b/README @@ -4,7 +4,7 @@ JSON in JavaScript Douglas Crockford douglas@crockford.com -2010-11-18 +2010-04-02 JSON is a light-weight, language independent, data interchange format. @@ -24,7 +24,9 @@ isn't already one, setting its value to an object containing a stringify method and a parse method. The parse method uses the eval method to do the parsing, guarding it with several regular expressions to defend against accidental code execution hazards. On current browsers, this file does nothing, -prefering the built-in JSON object. +preferring the built-in JSON object. There is no reason to use this file unless +fate compels you to support IE8, which is something that no one should ever +have to do again. json.js: This file does everything that json2.js does. It also adds a toJSONString method and a parseJSON method to Object.prototype. Use of this From 5b81c7b381c505a5a47166df0e2c41060106c4c7 Mon Sep 17 00:00:00 2001 From: Douglas Crockford Date: Fri, 1 May 2015 18:07:21 -0700 Subject: [PATCH 07/28] ternary --- json.js | 17 +++++++++-------- json2.js | 17 +++++++++-------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/json.js b/json.js index 8f5d91e..d188cb2 100644 --- a/json.js +++ b/json.js @@ -1,6 +1,6 @@ /* json.js - 2014-02-25 + 2015-05-01 Public Domain @@ -343,8 +343,8 @@ if (typeof JSON !== 'object') { v = partial.length === 0 ? '[]' : gap - ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' - : '[' + partial.join(',') + ']'; + ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' + : '[' + partial.join(',') + ']'; gap = mind; return v; } @@ -386,8 +386,8 @@ if (typeof JSON !== 'object') { v = partial.length === 0 ? '{}' : gap - ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' - : '{' + partial.join(',') + '}'; + ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' + : '{' + partial.join(',') + '}'; gap = mind; return v; } @@ -510,9 +510,10 @@ if (typeof JSON !== 'object') { // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. if (/^[\],:{}\s]*$/.test( - text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') - .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') - .replace(/(?:^|:|,)(?:\s*\[)+/g, '') + text + .replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') + .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') + .replace(/(?:^|:|,)(?:\s*\[)+/g, '') )) { // In the third stage we use the eval function to compile the text into a diff --git a/json2.js b/json2.js index d403181..bc48164 100644 --- a/json2.js +++ b/json2.js @@ -1,6 +1,6 @@ /* json2.js - 2015-02-25 + 2015-05-01 Public Domain. @@ -311,8 +311,8 @@ if (typeof JSON !== 'object') { v = partial.length === 0 ? '[]' : gap - ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' - : '[' + partial.join(',') + ']'; + ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' + : '[' + partial.join(',') + ']'; gap = mind; return v; } @@ -358,8 +358,8 @@ if (typeof JSON !== 'object') { v = partial.length === 0 ? '{}' : gap - ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' - : '{' + partial.join(',') + '}'; + ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' + : '{' + partial.join(',') + '}'; gap = mind; return v; } @@ -483,9 +483,10 @@ if (typeof JSON !== 'object') { if ( /^[\],:{}\s]*$/.test( - text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') - .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') - .replace(/(?:^|:|,)(?:\s*\[)+/g, '') + text + .replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') + .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') + .replace(/(?:^|:|,)(?:\s*\[)+/g, '') ) ) { From 1e3869cb398ddf58d3d52efd735067093dc5bf3e Mon Sep 17 00:00:00 2001 From: Douglas Crockford Date: Sat, 2 May 2015 09:19:02 -0700 Subject: [PATCH 08/28] indent ternary --- json.js | 79 ++++++++++++++++++++++++--------------------- json2.js | 75 +++++++++++++++++++++--------------------- json_parse.js | 34 +++++++++---------- json_parse_state.js | 72 ++++++++++++++++++++--------------------- 4 files changed, 133 insertions(+), 127 deletions(-) diff --git a/json.js b/json.js index d188cb2..5b0928f 100644 --- a/json.js +++ b/json.js @@ -1,6 +1,6 @@ /* json.js - 2015-05-01 + 2015-05-02 Public Domain @@ -47,7 +47,9 @@ // convert the value to a date. myData = text.parseJSON(function (key, value) { - return key.indexOf('date') >= 0 ? new Date(value) : value; + return key.indexOf('date') >= 0 + ? new Date(value) + : value; }); This file will break programs with improper for..in loops. See @@ -85,7 +87,9 @@ Date.prototype.toJSON = function (key) { function f(n) { // Format integers to have at least two digits. - return n < 10 ? '0' + n : n; + return n < 10 + ? '0' + n + : n; } return this.getUTCFullYear() + '-' + @@ -131,8 +135,9 @@ // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' text = JSON.stringify([new Date()], function (key, value) { - return this[key] instanceof Date ? - 'Date(' + this[key] + ')' : value; + return this[key] instanceof Date + ? 'Date(' + this[key] + ')' + : value; }); // text is '["Date(---current time---)"]' @@ -205,8 +210,8 @@ if (typeof JSON !== 'object') { function f(n) { // Format integers to have at least two digits. return n < 10 - ? '0' + n - : n; + ? '0' + n + : n; } function this_value() { @@ -218,13 +223,13 @@ if (typeof JSON !== 'object') { Date.prototype.toJSON = function (ignore) { return isFinite(this.valueOf()) - ? this.getUTCFullYear() + '-' + - f(this.getUTCMonth() + 1) + '-' + - f(this.getUTCDate()) + 'T' + - f(this.getUTCHours()) + ':' + - f(this.getUTCMinutes()) + ':' + - f(this.getUTCSeconds()) + 'Z' - : null; + ? this.getUTCFullYear() + '-' + + f(this.getUTCMonth() + 1) + '-' + + f(this.getUTCDate()) + 'T' + + f(this.getUTCHours()) + ':' + + f(this.getUTCMinutes()) + ':' + + f(this.getUTCSeconds()) + 'Z' + : null; }; Boolean.prototype.toJSON = this_value; @@ -249,13 +254,13 @@ if (typeof JSON !== 'object') { escapable.lastIndex = 0; return escapable.test(string) - ? '"' + string.replace(escapable, function (a) { - var c = meta[a]; - return typeof c === 'string' - ? c - : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); - }) + '"' - : '"' + string + '"'; + ? '"' + string.replace(escapable, function (a) { + var c = meta[a]; + return typeof c === 'string' + ? c + : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }) + '"' + : '"' + string + '"'; } @@ -296,8 +301,8 @@ if (typeof JSON !== 'object') { // JSON numbers must be finite. Encode non-finite numbers as null. return isFinite(value) - ? String(value) - : 'null'; + ? String(value) + : 'null'; case 'boolean': case 'null': @@ -341,10 +346,10 @@ if (typeof JSON !== 'object') { // brackets. v = partial.length === 0 - ? '[]' - : gap - ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' - : '[' + partial.join(',') + ']'; + ? '[]' + : gap + ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' + : '[' + partial.join(',') + ']'; gap = mind; return v; } @@ -359,8 +364,8 @@ if (typeof JSON !== 'object') { v = str(k, value); if (v) { partial.push(quote(k) + (gap - ? ': ' - : ':') + v); + ? ': ' + : ':') + v); } } } @@ -373,8 +378,8 @@ if (typeof JSON !== 'object') { v = str(k, value); if (v) { partial.push(quote(k) + (gap - ? ': ' - : ':') + v); + ? ': ' + : ':') + v); } } } @@ -384,10 +389,10 @@ if (typeof JSON !== 'object') { // and wrap them in braces. v = partial.length === 0 - ? '{}' - : gap - ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' - : '{' + partial.join(',') + '}'; + ? '{}' + : gap + ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' + : '{' + partial.join(',') + '}'; gap = mind; return v; } @@ -527,8 +532,8 @@ if (typeof JSON !== 'object') { // each name/value pair to a reviver function for possible transformation. return typeof reviver === 'function' - ? walk({'': j}, '') - : j; + ? walk({'': j}, '') + : j; } // If the text is not JSON parseable, then a SyntaxError is thrown. diff --git a/json2.js b/json2.js index bc48164..119bf9a 100644 --- a/json2.js +++ b/json2.js @@ -1,6 +1,6 @@ /* json2.js - 2015-05-01 + 2015-05-02 Public Domain. @@ -49,8 +49,8 @@ function f(n) { // Format integers to have at least two digits. return n < 10 - ? '0' + n - : n; + ? '0' + n + : n; } return this.getUTCFullYear() + '-' + @@ -96,8 +96,9 @@ // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' text = JSON.stringify([new Date()], function (key, value) { - return this[key] instanceof Date ? - 'Date(' + this[key] + ')' : value; + return this[key] instanceof Date + ? 'Date(' + this[key] + ')' + : value; }); // text is '["Date(---current time---)"]' @@ -173,8 +174,8 @@ if (typeof JSON !== 'object') { function f(n) { // Format integers to have at least two digits. return n < 10 - ? '0' + n - : n; + ? '0' + n + : n; } function this_value() { @@ -186,13 +187,13 @@ if (typeof JSON !== 'object') { Date.prototype.toJSON = function () { return isFinite(this.valueOf()) - ? this.getUTCFullYear() + '-' + - f(this.getUTCMonth() + 1) + '-' + - f(this.getUTCDate()) + 'T' + - f(this.getUTCHours()) + ':' + - f(this.getUTCMinutes()) + ':' + - f(this.getUTCSeconds()) + 'Z' - : null; + ? this.getUTCFullYear() + '-' + + f(this.getUTCMonth() + 1) + '-' + + f(this.getUTCDate()) + 'T' + + f(this.getUTCHours()) + ':' + + f(this.getUTCMinutes()) + ':' + + f(this.getUTCSeconds()) + 'Z' + : null; }; Boolean.prototype.toJSON = this_value; @@ -217,13 +218,13 @@ if (typeof JSON !== 'object') { escapable.lastIndex = 0; return escapable.test(string) - ? '"' + string.replace(escapable, function (a) { - var c = meta[a]; - return typeof c === 'string' - ? c - : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); - }) + '"' - : '"' + string + '"'; + ? '"' + string.replace(escapable, function (a) { + var c = meta[a]; + return typeof c === 'string' + ? c + : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }) + '"' + : '"' + string + '"'; } @@ -264,8 +265,8 @@ if (typeof JSON !== 'object') { // JSON numbers must be finite. Encode non-finite numbers as null. return isFinite(value) - ? String(value) - : 'null'; + ? String(value) + : 'null'; case 'boolean': case 'null': @@ -309,10 +310,10 @@ if (typeof JSON !== 'object') { // brackets. v = partial.length === 0 - ? '[]' - : gap - ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' - : '[' + partial.join(',') + ']'; + ? '[]' + : gap + ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' + : '[' + partial.join(',') + ']'; gap = mind; return v; } @@ -328,8 +329,8 @@ if (typeof JSON !== 'object') { if (v) { partial.push(quote(k) + ( gap - ? ': ' - : ':' + ? ': ' + : ':' ) + v); } } @@ -344,8 +345,8 @@ if (typeof JSON !== 'object') { if (v) { partial.push(quote(k) + ( gap - ? ': ' - : ':' + ? ': ' + : ':' ) + v); } } @@ -356,10 +357,10 @@ if (typeof JSON !== 'object') { // and wrap them in braces. v = partial.length === 0 - ? '{}' - : gap - ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' - : '{' + partial.join(',') + '}'; + ? '{}' + : gap + ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' + : '{' + partial.join(',') + '}'; gap = mind; return v; } @@ -501,8 +502,8 @@ if (typeof JSON !== 'object') { // each name/value pair to a reviver function for possible transformation. return typeof reviver === 'function' - ? walk({'': j}, '') - : j; + ? walk({'': j}, '') + : j; } // If the text is not JSON parseable, then a SyntaxError is thrown. diff --git a/json_parse.js b/json_parse.js index cd96c3c..c8ed39d 100644 --- a/json_parse.js +++ b/json_parse.js @@ -1,6 +1,6 @@ /* json_parse.js - 2015-02-25 + 2015-05-02 Public Domain. @@ -306,8 +306,8 @@ var json_parse = (function () { return number(); default: return ch >= '0' && ch <= '9' - ? number() - : word(); + ? number() + : word(); } }; @@ -333,22 +333,22 @@ var json_parse = (function () { // result. return typeof reviver === 'function' - ? (function walk(holder, key) { - var k, v, value = holder[key]; - if (value && typeof value === 'object') { - for (k in value) { - if (Object.prototype.hasOwnProperty.call(value, k)) { - v = walk(value, k); - if (v !== undefined) { - value[k] = v; - } else { - delete value[k]; + ? (function walk(holder, key) { + var k, v, value = holder[key]; + if (value && typeof value === 'object') { + for (k in value) { + if (Object.prototype.hasOwnProperty.call(value, k)) { + v = walk(value, k); + if (v !== undefined) { + value[k] = v; + } else { + delete value[k]; + } } } } - } - return reviver.call(holder, key, value); - }({'': result}, '')) - : result; + return reviver.call(holder, key, value); + }({'': result}, '')) + : result; }; }()); diff --git a/json_parse_state.js b/json_parse_state.js index 163631d..beb0f97 100644 --- a/json_parse_state.js +++ b/json_parse_state.js @@ -1,6 +1,6 @@ /* json_parse_state.js - 2015-02-25 + 2015-05-02 Public Domain. @@ -288,9 +288,9 @@ var json_parse = (function () { // Remove and replace any backslash escapement. return text.replace(/\\(?:u(.{4})|([^u]))/g, function (ignore, b, c) { - return b - ? String.fromCharCode(parseInt(b, 16)) - : escapes[c]; + return b + ? String.fromCharCode(parseInt(b, 16)) + : escapes[c]; }); } @@ -299,7 +299,7 @@ var json_parse = (function () { // A regular expression is used to extract tokens from the JSON text. // The extraction process is cautious. - var r, // The result of the exec method. + var result, tx = /^[\u0020\t\n\r]*(?:([,:\[\]{}]|true|false|null)|(-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)|"((?:[^\r\n\t\\\"]|\\(?:["\\\/trnfb]|u[0-9a-fA-F]{4}))*)")/; // Set the starting state. @@ -318,36 +318,36 @@ var json_parse = (function () { // For each token... while (true) { - r = tx.exec(source); - if (!r) { + result = tx.exec(source); + if (!result) { break; } -// r is the result array from matching the tokenizing regular expression. -// r[0] contains everything that matched, including any initial whitespace. -// r[1] contains any punctuation that was matched, or true, false, or null. -// r[2] contains a matched number, still in string form. -// r[3] contains a matched string, without quotes but with escapement. +// result is the result array from matching the tokenizing regular expression. +// result[0] contains everything that matched, including any initial whitespace. +// result[1] contains any punctuation that was matched, or true, false, or null. +// result[2] contains a matched number, still in string form. +// result[3] contains a matched string, without quotes but with escapement. - if (r[1]) { + if (result[1]) { // Token: Execute the action for this state and token. - action[r[1]][state](); + action[result[1]][state](); - } else if (r[2]) { + } else if (result[2]) { // Number token: Convert the number string into a number value and execute // the action for this state and number. - value = +r[2]; + value = +result[2]; number[state](); } else { // String token: Replace the escapement sequences and execute the action for // this state and string. - value = debackslashify(r[3]); + value = debackslashify(result[3]); string[state](); } @@ -355,7 +355,7 @@ var json_parse = (function () { // are tokens. This is a slow process, but it allows the use of ^ matching, // which assures that no illegal tokens slip through. - source = source.slice(r[0].length); + source = source.slice(result[0].length); } // If we find a state/token combination that is illegal, then the action will @@ -370,9 +370,9 @@ var json_parse = (function () { //a well-formed JSON text. if (state !== 'ok' || (/[^\u0020\t\n\r]/.test(source))) { - throw state instanceof SyntaxError - ? state - : new SyntaxError('JSON'); + throw state instanceof SyntaxError + ? state + : new SyntaxError('JSON'); } // If there is a reviver function, we recursively walk the new structure, @@ -381,23 +381,23 @@ var json_parse = (function () { // value in an empty key. If there is not a reviver function, we simply return // that value. - return typeof reviver === 'function' - ? (function walk(holder, key) { - var k, v, value = holder[key]; - if (value && typeof value === 'object') { - for (k in value) { - if (Object.prototype.hasOwnProperty.call(value, k)) { - v = walk(value, k); - if (v !== undefined) { - value[k] = v; - } else { - delete value[k]; + return typeof reviver === 'function' + ? (function walk(holder, key) { + var k, v, value = holder[key]; + if (value && typeof value === 'object') { + for (k in value) { + if (Object.prototype.hasOwnProperty.call(value, k)) { + v = walk(value, k); + if (v !== undefined) { + value[k] = v; + } else { + delete value[k]; + } } } } - } - return reviver.call(holder, key, value); - }({'': value}, '')) - : value; + return reviver.call(holder, key, value); + }({'': value}, '')) + : value; }; }()); From 40e6d9942cff3f58479e94655a875aeff809ca14 Mon Sep 17 00:00:00 2001 From: Douglas Crockford Date: Sun, 3 May 2015 06:03:29 -0700 Subject: [PATCH 09/28] obsolete --- json.js | 557 -------------------------------------------------------- 1 file changed, 557 deletions(-) delete mode 100644 json.js diff --git a/json.js b/json.js deleted file mode 100644 index 5b0928f..0000000 --- a/json.js +++ /dev/null @@ -1,557 +0,0 @@ -/* - json.js - 2015-05-02 - - Public Domain - - No warranty expressed or implied. Use at your own risk. - - This file has been superceded by http://www.JSON.org/json2.js - - See http://www.JSON.org/js.html - - This code should be minified before deployment. - See http://javascript.crockford.com/jsmin.html - - USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO - NOT CONTROL. - - This file adds these methods to JavaScript: - - object.toJSONString(whitelist) - This method produce a JSON text from a JavaScript value. - It must not contain any cyclical references. Illegal values - will be excluded. - - The default conversion for dates is to an ISO string. You can - add a toJSONString method to any date object to get a different - representation. - - The object and array methods can take an optional whitelist - argument. A whitelist is an array of strings. If it is provided, - keys in objects not found in the whitelist are excluded. - - string.parseJSON(filter) - This method parses a JSON text to produce an object or - array. It can throw a SyntaxError exception. - - The optional filter parameter is a function which can filter and - transform the results. It receives each of the keys and values, and - its return value is used instead of the original value. If it - returns what it received, then structure is not modified. If it - returns undefined then the member is deleted. - - Example: - - // Parse the text. If a key contains the string 'date' then - // convert the value to a date. - - myData = text.parseJSON(function (key, value) { - return key.indexOf('date') >= 0 - ? new Date(value) - : value; - }); - - This file will break programs with improper for..in loops. See - http://yuiblog.com/blog/2006/09/26/for-in-intrigue/ - - This file creates a global JSON object containing two methods: stringify - and parse. - - JSON.stringify(value, replacer, space) - value any JavaScript value, usually an object or array. - - replacer an optional parameter that determines how object - values are stringified for objects. It can be a - function or an array of strings. - - space an optional parameter that specifies the indentation - of nested structures. If it is omitted, the text will - be packed without extra whitespace. If it is a number, - it will specify the number of spaces to indent at each - level. If it is a string (such as '\t' or ' '), - it contains the characters used to indent at each level. - - This method produces a JSON text from a JavaScript value. - - When an object value is found, if the object contains a toJSON - method, its toJSON method will be called and the result will be - stringified. A toJSON method does not serialize: it returns the - value represented by the name/value pair that should be serialized, - or undefined if nothing should be serialized. The toJSON method - will be passed the key associated with the value, and this will be - bound to the object holding the key. - - For example, this would serialize Dates as ISO strings. - - Date.prototype.toJSON = function (key) { - function f(n) { - // Format integers to have at least two digits. - return n < 10 - ? '0' + n - : n; - } - - return this.getUTCFullYear() + '-' + - f(this.getUTCMonth() + 1) + '-' + - f(this.getUTCDate()) + 'T' + - f(this.getUTCHours()) + ':' + - f(this.getUTCMinutes()) + ':' + - f(this.getUTCSeconds()) + 'Z'; - }; - - You can provide an optional replacer method. It will be passed the - key and value of each member, with this bound to the containing - object. The value that is returned from your method will be - serialized. If your method returns undefined, then the member will - be excluded from the serialization. - - If the replacer parameter is an array of strings, then it will be - used to select the members to be serialized. It filters the results - such that only members with keys listed in the replacer array are - stringified. - - Values that do not have JSON representations, such as undefined or - functions, will not be serialized. Such values in objects will be - dropped; in arrays they will be replaced with null. You can use - a replacer function to replace those with JSON values. - JSON.stringify(undefined) returns undefined. - - The optional space parameter produces a stringification of the - value that is filled with line breaks and indentation to make it - easier to read. - - If the space parameter is a non-empty string, then that string will - be used for indentation. If the space parameter is a number, then - the indentation will be that many spaces. - - Example: - - text = JSON.stringify(['e', {pluribus: 'unum'}]); - // text is '["e",{"pluribus":"unum"}]' - - - text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); - // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' - - text = JSON.stringify([new Date()], function (key, value) { - return this[key] instanceof Date - ? 'Date(' + this[key] + ')' - : value; - }); - // text is '["Date(---current time---)"]' - - - JSON.parse(text, reviver) - This method parses a JSON text to produce an object or array. - It can throw a SyntaxError exception. - - The optional reviver parameter is a function that can filter and - transform the results. It receives each of the keys and values, - and its return value is used instead of the original value. - If it returns what it received, then the structure is not modified. - If it returns undefined then the member is deleted. - - Example: - - // Parse the text. Values that look like ISO date strings will - // be converted to Date objects. - - myData = JSON.parse(text, function (key, value) { - var a; - if (typeof value === 'string') { - a = -/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); - if (a) { - return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], - +a[5], +a[6])); - } - } - return value; - }); - - myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { - var d; - if (typeof value === 'string' && - value.slice(0, 5) === 'Date(' && - value.slice(-1) === ')') { - d = new Date(value.slice(5, -1)); - if (d) { - return d; - } - } - return value; - }); - - - This is a reference implementation. You are free to copy, modify, or - redistribute. -*/ - -/*jslint eval, for, this */ - -/*property - JSON, apply, call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, - getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, - lastIndex, length, parse, parseJSON, prototype, push, replace, slice, - stringify, test, toJSON, toJSONString, toString, valueOf -*/ - -// Create a JSON object only if one does not already exist. We create the -// methods in a closure to avoid creating global variables. - -if (typeof JSON !== 'object') { - JSON = {}; -} - -(function () { - 'use strict'; - - function f(n) { - // Format integers to have at least two digits. - return n < 10 - ? '0' + n - : n; - } - - function this_value() { - return this.valueOf(); - } - - if (typeof Date.prototype.toJSON !== 'function') { - - Date.prototype.toJSON = function (ignore) { - - return isFinite(this.valueOf()) - ? this.getUTCFullYear() + '-' + - f(this.getUTCMonth() + 1) + '-' + - f(this.getUTCDate()) + 'T' + - f(this.getUTCHours()) + ':' + - f(this.getUTCMinutes()) + ':' + - f(this.getUTCSeconds()) + 'Z' - : null; - }; - - Boolean.prototype.toJSON = this_value; - Number.prototype.toJSON = this_value; - String.prototype.toJSON = this_value; - } - - var cx, - escapable, - gap, - indent, - meta, - rep; - - - function quote(string) { - -// If the string contains no control characters, no quote characters, and no -// backslash characters, then we can safely slap some quotes around it. -// Otherwise we must also replace the offending characters with safe escape -// sequences. - - escapable.lastIndex = 0; - return escapable.test(string) - ? '"' + string.replace(escapable, function (a) { - var c = meta[a]; - return typeof c === 'string' - ? c - : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); - }) + '"' - : '"' + string + '"'; - } - - - function str(key, holder) { - -// Produce a string from holder[key]. - - var i, // The loop counter. - k, // The member key. - v, // The member value. - length, - mind = gap, - partial, - value = holder[key]; - -// If the value has a toJSON method, call it to obtain a replacement value. - - if (value && typeof value === 'object' && - typeof value.toJSON === 'function') { - value = value.toJSON(key); - } - -// If we were called with a replacer function, then call the replacer to -// obtain a replacement value. - - if (typeof rep === 'function') { - value = rep.call(holder, key, value); - } - -// What happens next depends on the value's type. - - switch (typeof value) { - case 'string': - return quote(value); - - case 'number': - -// JSON numbers must be finite. Encode non-finite numbers as null. - - return isFinite(value) - ? String(value) - : 'null'; - - case 'boolean': - case 'null': - -// If the value is a boolean or null, convert it to a string. Note: -// typeof null does not produce 'null'. The case is included here in -// the remote chance that this gets fixed someday. - - return String(value); - -// If the type is 'object', we might be dealing with an object or an array or -// null. - - case 'object': - -// Due to a specification blunder in ECMAScript, typeof null is 'object', -// so watch out for that case. - - if (!value) { - return 'null'; - } - -// Make an array to hold the partial results of stringifying this object value. - - gap += indent; - partial = []; - -// Is the value an array? - - if (Object.prototype.toString.apply(value) === '[object Array]') { - -// The value is an array. Stringify every element. Use null as a placeholder -// for non-JSON values. - - length = value.length; - for (i = 0; i < length; i += 1) { - partial[i] = str(i, value) || 'null'; - } - -// Join all of the elements together, separated with commas, and wrap them in -// brackets. - - v = partial.length === 0 - ? '[]' - : gap - ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' - : '[' + partial.join(',') + ']'; - gap = mind; - return v; - } - -// If the replacer is an array, use it to select the members to be stringified. - - if (rep && typeof rep === 'object') { - length = rep.length; - for (i = 0; i < length; i += 1) { - k = rep[i]; - if (typeof k === 'string') { - v = str(k, value); - if (v) { - partial.push(quote(k) + (gap - ? ': ' - : ':') + v); - } - } - } - } else { - -// Otherwise, iterate through all of the keys in the object. - - for (k in value) { - if (Object.prototype.hasOwnProperty.call(value, k)) { - v = str(k, value); - if (v) { - partial.push(quote(k) + (gap - ? ': ' - : ':') + v); - } - } - } - } - -// Join all of the member texts together, separated with commas, -// and wrap them in braces. - - v = partial.length === 0 - ? '{}' - : gap - ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' - : '{' + partial.join(',') + '}'; - gap = mind; - return v; - } - } - -// If the JSON object does not yet have a stringify method, give it one. - - if (typeof JSON.stringify !== 'function') { - escapable = /[\\\"\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; - meta = { // table of character substitutions - '\b': '\\b', - '\t': '\\t', - '\n': '\\n', - '\f': '\\f', - '\r': '\\r', - '"': '\\"', - '\\': '\\\\' - }; - JSON.stringify = function (value, replacer, space) { - -// The stringify method takes a value and an optional replacer, and an optional -// space parameter, and returns a JSON text. The replacer can be a function -// that can replace values, or an array of strings that will select the keys. -// A default replacer method can be provided. Use of the space parameter can -// produce text that is more easily readable. - - var i; - gap = ''; - indent = ''; - -// If the space parameter is a number, make an indent string containing that -// many spaces. - - if (typeof space === 'number') { - for (i = 0; i < space; i += 1) { - indent += ' '; - } - -// If the space parameter is a string, it will be used as the indent string. - - } else if (typeof space === 'string') { - indent = space; - } - -// If there is a replacer, it must be a function or an array. -// Otherwise, throw an error. - - rep = replacer; - if (replacer && typeof replacer !== 'function' && - (typeof replacer !== 'object' || - typeof replacer.length !== 'number')) { - throw new Error('JSON.stringify'); - } - -// Make a fake root object containing our value under the key of ''. -// Return the result of stringifying the value. - - return str('', {'': value}); - }; - } - - -// If the JSON object does not yet have a parse method, give it one. - - if (typeof JSON.parse !== 'function') { - cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; - JSON.parse = function (text, reviver) { - -// The parse method takes a text and an optional reviver function, and returns -// a JavaScript value if the text is a valid JSON text. - - var j; - - function walk(holder, key) { - -// The walk method is used to recursively walk the resulting structure so -// that modifications can be made. - - var k, v, value = holder[key]; - if (value && typeof value === 'object') { - for (k in value) { - if (Object.prototype.hasOwnProperty.call(value, k)) { - v = walk(value, k); - if (v !== undefined) { - value[k] = v; - } else { - delete value[k]; - } - } - } - } - return reviver.call(holder, key, value); - } - - -// Parsing happens in four stages. In the first stage, we replace certain -// Unicode characters with escape sequences. JavaScript handles many characters -// incorrectly, either silently deleting them, or treating them as line endings. - - text = String(text); - cx.lastIndex = 0; - if (cx.test(text)) { - text = text.replace(cx, function (a) { - return '\\u' + - ('0000' + a.charCodeAt(0).toString(16)).slice(-4); - }); - } - -// In the second stage, we run the text against regular expressions that look -// for non-JSON patterns. We are especially concerned with '()' and 'new' -// because they can cause invocation, and '=' because it can cause mutation. -// But just to be safe, we want to reject all unexpected forms. - -// We split the second stage into 4 regexp operations in order to work around -// crippling inefficiencies in IE's and Safari's regexp engines. First we -// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we -// replace all simple value tokens with ']' characters. Third, we delete all -// open brackets that follow a colon or comma or that begin the text. Finally, -// we look to see that the remaining characters are only whitespace or ']' or -// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. - - if (/^[\],:{}\s]*$/.test( - text - .replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') - .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') - .replace(/(?:^|:|,)(?:\s*\[)+/g, '') - )) { - -// In the third stage we use the eval function to compile the text into a -// JavaScript structure. The '{' operator is subject to a syntactic ambiguity -// in JavaScript: it can begin a block or an object literal. We wrap the text -// in parens to eliminate the ambiguity. - - j = eval('(' + text + ')'); - -// In the optional fourth stage, we recursively walk the new structure, passing -// each name/value pair to a reviver function for possible transformation. - - return typeof reviver === 'function' - ? walk({'': j}, '') - : j; - } - -// If the text is not JSON parseable, then a SyntaxError is thrown. - - throw new SyntaxError('JSON.parse'); - }; - } - -// Augment the basic prototypes if they have not already been augmented. -// These forms are obsolete. It is recommended that JSON.stringify and -// JSON.parse be used instead. - - if (!Object.prototype.toJSONString) { - Object.prototype.toJSONString = function (filter) { - return JSON.stringify(this, filter); - }; - Object.prototype.parseJSON = function (filter) { - return JSON.parse(this, filter); - }; - } -}()); From 04768c45a73fa390d2e856f8958690535ed8588f Mon Sep 17 00:00:00 2001 From: Douglas Crockford Date: Sun, 3 May 2015 06:03:46 -0700 Subject: [PATCH 10/28] rx --- json2.js | 43 ++++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/json2.js b/json2.js index 119bf9a..5838457 100644 --- a/json2.js +++ b/json2.js @@ -1,6 +1,6 @@ /* json2.js - 2015-05-02 + 2015-05-03 Public Domain. @@ -17,7 +17,9 @@ This file creates a global JSON object containing two methods: stringify - and parse. + and parse. This file is provides the ES5 JSON capability to ES3 systems. + If a project might run on IE8 or earlier, then this file should be included. + This file does nothing on ES5 systems. JSON.stringify(value, replacer, space) value any JavaScript value, usually an object or array. @@ -170,6 +172,13 @@ if (typeof JSON !== 'object') { (function () { 'use strict'; + + var rx_one = /^[\],:{}\s]*$/, + rx_two = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, + rx_three = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, + rx_four = /(?:^|:|,)(?:\s*\[)+/g, + rx_escapable = /[\\\"\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, + rx_dangerous = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; function f(n) { // Format integers to have at least two digits. @@ -201,9 +210,7 @@ if (typeof JSON !== 'object') { String.prototype.toJSON = this_value; } - var cx, - escapable, - gap, + var gap, indent, meta, rep; @@ -216,13 +223,13 @@ if (typeof JSON !== 'object') { // Otherwise we must also replace the offending characters with safe escape // sequences. - escapable.lastIndex = 0; - return escapable.test(string) - ? '"' + string.replace(escapable, function (a) { + rx_escapable.lastIndex = 0; + return rx_escapable.test(string) + ? '"' + string.replace(rx_escapable, function (a) { var c = meta[a]; return typeof c === 'string' - ? c - : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + ? c + : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); }) + '"' : '"' + string + '"'; } @@ -369,7 +376,6 @@ if (typeof JSON !== 'object') { // If the JSON object does not yet have a stringify method, give it one. if (typeof JSON.stringify !== 'function') { - escapable = /[\\\"\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; meta = { // table of character substitutions '\b': '\\b', '\t': '\\t', @@ -426,7 +432,6 @@ if (typeof JSON !== 'object') { // If the JSON object does not yet have a parse method, give it one. if (typeof JSON.parse !== 'function') { - cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; JSON.parse = function (text, reviver) { // The parse method takes a text and an optional reviver function, and returns @@ -461,9 +466,9 @@ if (typeof JSON !== 'object') { // incorrectly, either silently deleting them, or treating them as line endings. text = String(text); - cx.lastIndex = 0; - if (cx.test(text)) { - text = text.replace(cx, function (a) { + rx_dangerous.lastIndex = 0; + if (rx_dangerous.test(text)) { + text = text.replace(rx_dangerous, function (a) { return '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); }); @@ -483,11 +488,11 @@ if (typeof JSON !== 'object') { // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. if ( - /^[\],:{}\s]*$/.test( + rx_one.test( text - .replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') - .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') - .replace(/(?:^|:|,)(?:\s*\[)+/g, '') + .replace(rx_two, '@') + .replace(rx_three, ']') + .replace(rx_four, '') ) ) { From c98948ae1944a28e2e8ebc3717894e580aeaaa05 Mon Sep 17 00:00:00 2001 From: Douglas Crockford Date: Sun, 3 May 2015 06:04:38 -0700 Subject: [PATCH 11/28] remove json.js --- README | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/README b/README index 3f245fe..6ca2c8c 100644 --- a/README +++ b/README @@ -4,7 +4,7 @@ JSON in JavaScript Douglas Crockford douglas@crockford.com -2010-04-02 +2015-05-03 JSON is a light-weight, language independent, data interchange format. @@ -28,10 +28,6 @@ preferring the built-in JSON object. There is no reason to use this file unless fate compels you to support IE8, which is something that no one should ever have to do again. -json.js: This file does everything that json2.js does. It also adds a -toJSONString method and a parseJSON method to Object.prototype. Use of this -file is not recommended. - json_parse.js: This file contains an alternative JSON parse function that uses recursive descent instead of eval. @@ -40,5 +36,5 @@ uses a state machine instead of eval. cycle.js: This file contains two functions, JSON.decycle and JSON.retrocycle, which make it possible to encode cyclical structures and dags in JSON, and to -then recover them. JSONPath is used to represent the links. -http://GOESSNER.net/articles/JsonPath/ +then recover them. This is a capability that is not provided by ES5. JSONPath +is used to represent the links. [http://GOESSNER.net/articles/JsonPath/] From bdeb882f4e7dd15ae8763c48c86af9a019ecbce9 Mon Sep 17 00:00:00 2001 From: Douglas Crockford Date: Tue, 3 May 2016 12:32:00 -0700 Subject: [PATCH 12/28] // --- json2.js | 357 +++++++++++++++++++++++++++---------------------------- 1 file changed, 172 insertions(+), 185 deletions(-) diff --git a/json2.js b/json2.js index 5838457..bb18573 100644 --- a/json2.js +++ b/json2.js @@ -1,158 +1,143 @@ -/* - json2.js - 2015-05-03 - - Public Domain. - - NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. - - See http://www.JSON.org/js.html - - - This code should be minified before deployment. - See http://javascript.crockford.com/jsmin.html - - USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO - NOT CONTROL. - - - This file creates a global JSON object containing two methods: stringify - and parse. This file is provides the ES5 JSON capability to ES3 systems. - If a project might run on IE8 or earlier, then this file should be included. - This file does nothing on ES5 systems. - - JSON.stringify(value, replacer, space) - value any JavaScript value, usually an object or array. - - replacer an optional parameter that determines how object - values are stringified for objects. It can be a - function or an array of strings. - - space an optional parameter that specifies the indentation - of nested structures. If it is omitted, the text will - be packed without extra whitespace. If it is a number, - it will specify the number of spaces to indent at each - level. If it is a string (such as '\t' or ' '), - it contains the characters used to indent at each level. - - This method produces a JSON text from a JavaScript value. - - When an object value is found, if the object contains a toJSON - method, its toJSON method will be called and the result will be - stringified. A toJSON method does not serialize: it returns the - value represented by the name/value pair that should be serialized, - or undefined if nothing should be serialized. The toJSON method - will be passed the key associated with the value, and this will be - bound to the value - - For example, this would serialize Dates as ISO strings. - - Date.prototype.toJSON = function (key) { - function f(n) { - // Format integers to have at least two digits. - return n < 10 - ? '0' + n - : n; - } - - return this.getUTCFullYear() + '-' + - f(this.getUTCMonth() + 1) + '-' + - f(this.getUTCDate()) + 'T' + - f(this.getUTCHours()) + ':' + - f(this.getUTCMinutes()) + ':' + - f(this.getUTCSeconds()) + 'Z'; - }; - - You can provide an optional replacer method. It will be passed the - key and value of each member, with this bound to the containing - object. The value that is returned from your method will be - serialized. If your method returns undefined, then the member will - be excluded from the serialization. - - If the replacer parameter is an array of strings, then it will be - used to select the members to be serialized. It filters the results - such that only members with keys listed in the replacer array are - stringified. - - Values that do not have JSON representations, such as undefined or - functions, will not be serialized. Such values in objects will be - dropped; in arrays they will be replaced with null. You can use - a replacer function to replace those with JSON values. - JSON.stringify(undefined) returns undefined. - - The optional space parameter produces a stringification of the - value that is filled with line breaks and indentation to make it - easier to read. - - If the space parameter is a non-empty string, then that string will - be used for indentation. If the space parameter is a number, then - the indentation will be that many spaces. - - Example: - - text = JSON.stringify(['e', {pluribus: 'unum'}]); - // text is '["e",{"pluribus":"unum"}]' - - - text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); - // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' - - text = JSON.stringify([new Date()], function (key, value) { - return this[key] instanceof Date - ? 'Date(' + this[key] + ')' - : value; - }); - // text is '["Date(---current time---)"]' - - - JSON.parse(text, reviver) - This method parses a JSON text to produce an object or array. - It can throw a SyntaxError exception. - - The optional reviver parameter is a function that can filter and - transform the results. It receives each of the keys and values, - and its return value is used instead of the original value. - If it returns what it received, then the structure is not modified. - If it returns undefined then the member is deleted. - - Example: - - // Parse the text. Values that look like ISO date strings will - // be converted to Date objects. - - myData = JSON.parse(text, function (key, value) { - var a; - if (typeof value === 'string') { - a = -/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); - if (a) { - return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], - +a[5], +a[6])); - } - } - return value; - }); - - myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { - var d; - if (typeof value === 'string' && - value.slice(0, 5) === 'Date(' && - value.slice(-1) === ')') { - d = new Date(value.slice(5, -1)); - if (d) { - return d; - } - } - return value; - }); - - - This is a reference implementation. You are free to copy, modify, or - redistribute. -*/ - -/*jslint - eval, for, this +// json2.js +// 2016-05-01 +// Public Domain. +// NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. +// See http://www.JSON.org/js.html +// This code should be minified before deployment. +// See http://javascript.crockford.com/jsmin.html + +// USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO +// NOT CONTROL. + +// This file creates a global JSON object containing two methods: stringify +// and parse. This file is provides the ES5 JSON capability to ES3 systems. +// If a project might run on IE8 or earlier, then this file should be included. +// This file does nothing on ES5 systems. + +// JSON.stringify(value, replacer, space) +// value any JavaScript value, usually an object or array. +// replacer an optional parameter that determines how object +// values are stringified for objects. It can be a +// function or an array of strings. +// space an optional parameter that specifies the indentation +// of nested structures. If it is omitted, the text will +// be packed without extra whitespace. If it is a number, +// it will specify the number of spaces to indent at each +// level. If it is a string (such as '\t' or ' '), +// it contains the characters used to indent at each level. +// This method produces a JSON text from a JavaScript value. +// When an object value is found, if the object contains a toJSON +// method, its toJSON method will be called and the result will be +// stringified. A toJSON method does not serialize: it returns the +// value represented by the name/value pair that should be serialized, +// or undefined if nothing should be serialized. The toJSON method +// will be passed the key associated with the value, and this will be +// bound to the value. + +// For example, this would serialize Dates as ISO strings. + +// Date.prototype.toJSON = function (key) { +// function f(n) { +// // Format integers to have at least two digits. +// return n < 10 +// ? '0' + n +// : n; +// } +// return this.getUTCFullYear() + '-' + +// f(this.getUTCMonth() + 1) + '-' + +// f(this.getUTCDate()) + 'T' + +// f(this.getUTCHours()) + ':' + +// f(this.getUTCMinutes()) + ':' + +// f(this.getUTCSeconds()) + 'Z'; +// }; + +// You can provide an optional replacer method. It will be passed the +// key and value of each member, with this bound to the containing +// object. The value that is returned from your method will be +// serialized. If your method returns undefined, then the member will +// be excluded from the serialization. + +// If the replacer parameter is an array of strings, then it will be +// used to select the members to be serialized. It filters the results +// such that only members with keys listed in the replacer array are +// stringified. + +// Values that do not have JSON representations, such as undefined or +// functions, will not be serialized. Such values in objects will be +// dropped; in arrays they will be replaced with null. You can use +// a replacer function to replace those with JSON values. + +// JSON.stringify(undefined) returns undefined. + +// The optional space parameter produces a stringification of the +// value that is filled with line breaks and indentation to make it +// easier to read. + +// If the space parameter is a non-empty string, then that string will +// be used for indentation. If the space parameter is a number, then +// the indentation will be that many spaces. + +// Example: + +// text = JSON.stringify(['e', {pluribus: 'unum'}]); +// // text is '["e",{"pluribus":"unum"}]' + +// text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); +// // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' + +// text = JSON.stringify([new Date()], function (key, value) { +// return this[key] instanceof Date +// ? 'Date(' + this[key] + ')' +// : value; +// }); +// // text is '["Date(---current time---)"]' + +// JSON.parse(text, reviver) +// This method parses a JSON text to produce an object or array. +// It can throw a SyntaxError exception. + +// The optional reviver parameter is a function that can filter and +// transform the results. It receives each of the keys and values, +// and its return value is used instead of the original value. +// If it returns what it received, then the structure is not modified. +// If it returns undefined then the member is deleted. + +// Example: + +// // Parse the text. Values that look like ISO date strings will +// // be converted to Date objects. + +// myData = JSON.parse(text, function (key, value) { +// var a; +// if (typeof value === 'string') { +// a = +// /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); +// if (a) { +// return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], +// +a[5], +a[6])); +// } +// } +// return value; +// }); + +// myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { +// var d; +// if (typeof value === 'string' && +// value.slice(0, 5) === 'Date(' && +// value.slice(-1) === ')') { +// d = new Date(value.slice(5, -1)); +// if (d) { +// return d; +// } +// } +// return value; +// }); + +// This is a reference implementation. You are free to copy, modify, or +// redistribute. + +/*jslint + eval, for, this */ /*property @@ -172,21 +157,21 @@ if (typeof JSON !== 'object') { (function () { 'use strict'; - - var rx_one = /^[\],:{}\s]*$/, - rx_two = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, - rx_three = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, - rx_four = /(?:^|:|,)(?:\s*\[)+/g, - rx_escapable = /[\\\"\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, - rx_dangerous = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; + + var rx_one = /^[\],:{}\s]*$/; + var rx_two = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g; + var rx_three = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g; + var rx_four = /(?:^|:|,)(?:\s*\[)+/g; + var rx_escapable = /[\\\"\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; + var rx_dangerous = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; function f(n) { // Format integers to have at least two digits. - return n < 10 - ? '0' + n + return n < 10 + ? '0' + n : n; } - + function this_value() { return this.valueOf(); } @@ -210,10 +195,10 @@ if (typeof JSON !== 'object') { String.prototype.toJSON = this_value; } - var gap, - indent, - meta, - rep; + var gap; + var indent; + var meta; + var rep; function quote(string) { @@ -224,13 +209,13 @@ if (typeof JSON !== 'object') { // sequences. rx_escapable.lastIndex = 0; - return rx_escapable.test(string) + return rx_escapable.test(string) ? '"' + string.replace(rx_escapable, function (a) { var c = meta[a]; return typeof c === 'string' ? c : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); - }) + '"' + }) + '"' : '"' + string + '"'; } @@ -239,13 +224,13 @@ if (typeof JSON !== 'object') { // Produce a string from holder[key]. - var i, // The loop counter. - k, // The member key. - v, // The member value. - length, - mind = gap, - partial, - value = holder[key]; + var i; // The loop counter. + var k; // The member key. + var v; // The member value. + var length; + var mind = gap; + var partial; + var value = holder[key]; // If the value has a toJSON method, call it to obtain a replacement value. @@ -271,8 +256,8 @@ if (typeof JSON !== 'object') { // JSON numbers must be finite. Encode non-finite numbers as null. - return isFinite(value) - ? String(value) + return isFinite(value) + ? String(value) : 'null'; case 'boolean': @@ -335,8 +320,8 @@ if (typeof JSON !== 'object') { v = str(k, value); if (v) { partial.push(quote(k) + ( - gap - ? ': ' + gap + ? ': ' : ':' ) + v); } @@ -351,8 +336,8 @@ if (typeof JSON !== 'object') { v = str(k, value); if (v) { partial.push(quote(k) + ( - gap - ? ': ' + gap + ? ': ' : ':' ) + v); } @@ -444,7 +429,9 @@ if (typeof JSON !== 'object') { // The walk method is used to recursively walk the resulting structure so // that modifications can be made. - var k, v, value = holder[key]; + var k; + var v; + var value = holder[key]; if (value && typeof value === 'object') { for (k in value) { if (Object.prototype.hasOwnProperty.call(value, k)) { From a48e0c794456b5bf635be3b238f31845f82805aa Mon Sep 17 00:00:00 2001 From: Douglas Crockford Date: Tue, 3 May 2016 12:41:50 -0700 Subject: [PATCH 13/28] var --- json_parse.js | 332 +++++++++++++++++++++++++------------------------- 1 file changed, 167 insertions(+), 165 deletions(-) diff --git a/json_parse.js b/json_parse.js index c8ed39d..359015a 100644 --- a/json_parse.js +++ b/json_parse.js @@ -48,8 +48,8 @@ /*jslint for */ -/*property - at, b, call, charAt, f, fromCharCode, hasOwnProperty, message, n, name, +/*property + at, b, call, charAt, f, fromCharCode, hasOwnProperty, message, n, name, prototype, push, r, t, text */ @@ -64,230 +64,230 @@ var json_parse = (function () { // We are defining the function inside of another function to avoid creating // global variables. - var at, // The index of the current character - ch, // The current character - escapee = { - '"': '"', - '\\': '\\', - '/': '/', - b: '\b', - f: '\f', - n: '\n', - r: '\r', - t: '\t' - }, - text, - - error = function (m) { + 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; + + var error = function (m) { // Call error when something is wrong. - throw { - name: 'SyntaxError', - message: m, - at: at, - text: text - }; - }, + throw { + name: 'SyntaxError', + message: m, + at: at, + text: text + }; + }; - next = function (c) { + var next = function (c) { // If a c parameter is provided, verify that it matches the current character. - if (c && c !== ch) { - error("Expected '" + c + "' instead of '" + ch + "'"); - } + 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; - }, + ch = text.charAt(at); + at += 1; + return ch; + }; - number = function () { + var number = function () { // Parse a number value. - var number, - string = ''; + var number; + var string = ''; - if (ch === '-') { - string = '-'; - next('-'); + if (ch === '-') { + string = '-'; + next('-'); + } + while (ch >= '0' && ch <= '9') { + string += ch; + next(); + } + if (ch === '.') { + string += '.'; + while (next() && ch >= '0' && ch <= '9') { + string += ch; } - while (ch >= '0' && ch <= '9') { + } + if (ch === 'e' || ch === 'E') { + string += ch; + next(); + if (ch === '-' || ch === '+') { string += ch; next(); } - if (ch === '.') { - string += '.'; - while (next() && ch >= '0' && ch <= '9') { - string += ch; - } - } - if (ch === 'e' || ch === 'E') { + while (ch >= '0' && ch <= '9') { string += ch; next(); - if (ch === '-' || ch === '+') { - string += ch; - next(); - } - while (ch >= '0' && ch <= '9') { - string += ch; - next(); - } } - number = +string; - if (!isFinite(number)) { - error("Bad number"); - } else { - return number; - } - }, + } + number = +string; + if (!isFinite(number)) { + error("Bad number"); + } else { + return number; + } + }; - string = function () { + var string = function () { // Parse a string value. - var hex, - i, - string = '', - uffff; + var hex; + var i; + var string = ''; + var uffff; // When parsing for string values, we must look for " and \ characters. - if (ch === '"') { - while (next()) { - if (ch === '"') { - next(); - return string; - } - 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; + if (ch === '"') { + while (next()) { + if (ch === '"') { + next(); + return string; + } + if (ch === '\\') { + next(); + if (ch === 'u') { + uffff = 0; + for (i = 0; i < 4; i += 1) { + hex = parseInt(next(), 16); + if (!isFinite(hex)) { + break; } - string += String.fromCharCode(uffff); - } else if (typeof escapee[ch] === 'string') { - string += escapee[ch]; - } else { - break; + uffff = uffff * 16 + hex; } + string += String.fromCharCode(uffff); + } else if (typeof escapee[ch] === 'string') { + string += escapee[ch]; } else { - string += ch; + break; } + } else { + string += ch; } } - error("Bad string"); - }, + } + error("Bad string"); + }; - white = function () { + var white = function () { // Skip whitespace. - while (ch && ch <= ' ') { - next(); - } - }, + while (ch && ch <= ' ') { + next(); + } + }; - word = function () { + var word = function () { // true, false, or null. - 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; - } - error("Unexpected '" + ch + "'"); - }, + 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; + } + error("Unexpected '" + ch + "'"); + }; - value, // Place holder for the value function. + var value; // Place holder for the value function. - array = function () { + var array = function () { // Parse an array value. - var array = []; + var array = []; - if (ch === '[') { - next('['); + if (ch === '[') { + next('['); + white(); + if (ch === ']') { + next(']'); + return array; // empty array + } + while (ch) { + array.push(value()); white(); if (ch === ']') { next(']'); - return array; // empty array - } - while (ch) { - array.push(value()); - white(); - if (ch === ']') { - next(']'); - return array; - } - next(','); - white(); + return array; } + next(','); + white(); } - error("Bad array"); - }, + } + error("Bad array"); + }; - object = function () { + var object = function () { // Parse an object value. - var key, - object = {}; + var key; + var object = {}; - if (ch === '{') { - next('{'); + if (ch === '{') { + next('{'); + white(); + if (ch === '}') { + next('}'); + return object; // empty object + } + while (ch) { + key = string(); + white(); + next(':'); + if (Object.hasOwnProperty.call(object, key)) { + error('Duplicate key "' + key + '"'); + } + object[key] = value(); white(); if (ch === '}') { next('}'); - return object; // empty object - } - while (ch) { - key = string(); - white(); - next(':'); - if (Object.hasOwnProperty.call(object, key)) { - error('Duplicate key "' + key + '"'); - } - object[key] = value(); - white(); - if (ch === '}') { - next('}'); - return object; - } - next(','); - white(); + return object; } + next(','); + white(); } - error("Bad object"); - }; + } + error("Bad object"); + }; value = function () { @@ -305,8 +305,8 @@ var json_parse = (function () { case '-': return number(); default: - return ch >= '0' && ch <= '9' - ? number() + return (ch >= '0' && ch <= '9') + ? number() : word(); } }; @@ -332,9 +332,11 @@ var json_parse = (function () { // in an empty key. If there is not a reviver function, we simply return the // result. - return typeof reviver === 'function' + return (typeof reviver === 'function') ? (function walk(holder, key) { - var k, v, value = holder[key]; + var k; + var v; + var value = holder[key]; if (value && typeof value === 'object') { for (k in value) { if (Object.prototype.hasOwnProperty.call(value, k)) { From ff3f6415b282220f13f85b52f92a1cc955caa2f2 Mon Sep 17 00:00:00 2001 From: Douglas Crockford Date: Sat, 7 May 2016 10:39:50 -0700 Subject: [PATCH 14/28] replacer --- cycle.js | 134 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 72 insertions(+), 62 deletions(-) diff --git a/cycle.js b/cycle.js index 0839437..7e00c5c 100644 --- a/cycle.js +++ b/cycle.js @@ -1,6 +1,6 @@ /* cycle.js - 2015-02-25 + 2016-05-01 Public Domain. @@ -15,60 +15,75 @@ /*jslint eval, for */ -/*property - $ref, apply, call, decycle, hasOwnProperty, length, prototype, push, - retrocycle, stringify, test, toString +/*property + $ref, decycle, forEach, isArray, keys, length, push, retrocycle, stringify, + test */ -if (typeof JSON.decycle !== 'function') { - JSON.decycle = function decycle(object) { - 'use strict'; +if (typeof JSON.decycle !== "function") { + JSON.decycle = function decycle(object, replacer) { + "use strict"; // Make a deep copy of an object or array, assuring that there is at most // one instance of each object or array in the resulting structure. The // duplicate references (which might be forming cycles) are replaced with // an object of the form -// {$ref: PATH} + +// {"$ref": PATH} + // where the PATH is a JSONPath string that locates the first occurance. + // So, + // var a = []; // a[0] = a; // return JSON.stringify(JSON.decycle(a)); + // produces the string '[{"$ref":"$"}]'. +// If a replacer function is provided, then it will be called for each value. +// A replacer function receives a value and returns a replacement value. + // JSONPath is used to locate the unique object. $ indicates the top level of -// the object or array. [NUMBER] or [STRING] indicates a child member or +// the object or array. [NUMBER] or [STRING] indicates a child element or // property. - var objects = [], // Keep a reference to each unique object or array - paths = []; // Keep the path to each unique object or array + var objects = []; // Keep a reference to each unique object or array + var paths = []; // Keep the path to each unique object or array return (function derez(value, path) { -// The derez recurses through the object, producing the deep copy. +// The derez function recurses through the object, producing the deep copy. + + var i; // The loop counter + var nu; // The new object or array - var i, // The loop counter - name, // Property name - nu; // The new object or array +// If a replacer function was provided, then call it to get a replacement value. -// typeof null === 'object', so go on if this value is really an object but not + if (replacer !== undefined) { + value = replacer(value); + } + +// typeof null === "object", so go on if this value is really an object but not // one of the weird builtin objects. - if (typeof value === 'object' && value !== null && - !(value instanceof Boolean) && - !(value instanceof Date) && - !(value instanceof Number) && - !(value instanceof RegExp) && - !(value instanceof String)) { + if ( + typeof value === "object" && value !== null && + !(value instanceof Boolean) && + !(value instanceof Date) && + !(value instanceof Number) && + !(value instanceof RegExp) && + !(value instanceof String) + ) { // If the value is an object or array, look to see if we have already -// encountered it. If so, return a $ref/path object. This is a hard way, +// encountered it. If so, return a {"$ref":PATH} object. This is a hard // linear search that will get slower as the number of unique objects grows. +// Someday, this should be replaced with an ES6 WeakMap. - for (i = 0; i < objects.length; i += 1) { - if (objects[i] === value) { - return {$ref: paths[i]}; - } + i = objects.indexOf(value); + if (i >= 0) { + return {$ref: paths[i]}; } // Otherwise, accumulate the unique value and its path. @@ -78,34 +93,34 @@ if (typeof JSON.decycle !== 'function') { // If it is an array, replicate the array. - if (Object.prototype.toString.apply(value) === '[object Array]') { + if (Array.isArray(value)) { nu = []; - for (i = 0; i < value.length; i += 1) { - nu[i] = derez(value[i], path + '[' + i + ']'); - } + value.forEach(function (element, i) { + nu[i] = derez(element, path + "[" + i + "]"); + }); } else { // If it is an object, replicate the object. nu = {}; - for (name in value) { - if (Object.prototype.hasOwnProperty.call(value, name)) { - nu[name] = derez(value[name], - path + '[' + JSON.stringify(name) + ']'); - } - } + Object.keys(value).forEach(function (name) { + nu[name] = derez( + value[name], + path + "[" + JSON.stringify(name) + "]" + ); + }); } return nu; } return value; - }(object, '$')); + }(object, "$")); }; } -if (typeof JSON.retrocycle !== 'function') { +if (typeof JSON.retrocycle !== "function") { JSON.retrocycle = function retrocycle($) { - 'use strict'; + "use strict"; // Restore an object that was reduced by decycle. Members whose values are // objects of the form @@ -135,35 +150,30 @@ if (typeof JSON.retrocycle !== 'function') { // replaces the $ref object with a reference to the value that is found by // the path. - var i, item, name, path; - - if (value && typeof value === 'object') { - if (Object.prototype.toString.apply(value) === '[object Array]') { - for (i = 0; i < value.length; i += 1) { - item = value[i]; - if (item && typeof item === 'object') { - path = item.$ref; - if (typeof path === 'string' && px.test(path)) { + if (value && typeof value === "object") { + if (Array.isArray(value)) { + value.forEach(function (element, i) { + if (typeof element === "object" && element !== null) { + var path = element.$ref; + if (typeof path === "string" && px.test(path)) { value[i] = eval(path); } else { - rez(item); + rez(element); } } - } + }); } else { - for (name in value) { - if (typeof value[name] === 'object') { - item = value[name]; - if (item) { - path = item.$ref; - if (typeof path === 'string' && px.test(path)) { - value[name] = eval(path); - } else { - rez(item); - } + Object.keys(value).forEach(function (name) { + var item = value[name]; + if (typeof item === "object" && item !== null) { + var path = item.$ref; + if (typeof path === "string" && px.test(path)) { + value[name] = eval(path); + } else { + rez(item); } } - } + }); } } }($)); From 107fc93c94aa3a9c7b48548631593ecf3aac60d2 Mon Sep 17 00:00:00 2001 From: Douglas Crockford Date: Tue, 10 May 2016 13:20:28 -0700 Subject: [PATCH 15/28] " --- json2.js | 190 ++++++++++----------- json_parse.js | 188 ++++++++++----------- json_parse_state.js | 398 ++++++++++++++++++++++---------------------- 3 files changed, 389 insertions(+), 387 deletions(-) diff --git a/json2.js b/json2.js index bb18573..d4720c0 100644 --- a/json2.js +++ b/json2.js @@ -23,7 +23,7 @@ // of nested structures. If it is omitted, the text will // be packed without extra whitespace. If it is a number, // it will specify the number of spaces to indent at each -// level. If it is a string (such as '\t' or ' '), +// level. If it is a string (such as "\t" or " "), // it contains the characters used to indent at each level. // This method produces a JSON text from a JavaScript value. // When an object value is found, if the object contains a toJSON @@ -39,16 +39,16 @@ // Date.prototype.toJSON = function (key) { // function f(n) { // // Format integers to have at least two digits. -// return n < 10 -// ? '0' + n +// return (n < 10) +// ? "0" + n // : n; // } -// return this.getUTCFullYear() + '-' + -// f(this.getUTCMonth() + 1) + '-' + -// f(this.getUTCDate()) + 'T' + -// f(this.getUTCHours()) + ':' + -// f(this.getUTCMinutes()) + ':' + -// f(this.getUTCSeconds()) + 'Z'; +// return this.getUTCFullYear() + "-" + +// f(this.getUTCMonth() + 1) + "-" + +// f(this.getUTCDate()) + "T" + +// f(this.getUTCHours()) + ":" + +// f(this.getUTCMinutes()) + ":" + +// f(this.getUTCSeconds()) + "Z"; // }; // You can provide an optional replacer method. It will be passed the @@ -79,15 +79,15 @@ // Example: -// text = JSON.stringify(['e', {pluribus: 'unum'}]); +// text = JSON.stringify(["e", {pluribus: "unum"}]); // // text is '["e",{"pluribus":"unum"}]' -// text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); +// text = JSON.stringify(["e", {pluribus: "unum"}], null, "\t"); // // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' // text = JSON.stringify([new Date()], function (key, value) { // return this[key] instanceof Date -// ? 'Date(' + this[key] + ')' +// ? "Date(" + this[key] + ")" // : value; // }); // // text is '["Date(---current time---)"]' @@ -109,7 +109,7 @@ // myData = JSON.parse(text, function (key, value) { // var a; -// if (typeof value === 'string') { +// if (typeof value === "string") { // a = // /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); // if (a) { @@ -122,9 +122,9 @@ // myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { // var d; -// if (typeof value === 'string' && -// value.slice(0, 5) === 'Date(' && -// value.slice(-1) === ')') { +// if (typeof value === "string" && +// value.slice(0, 5) === "Date(" && +// value.slice(-1) === ")") { // d = new Date(value.slice(5, -1)); // if (d) { // return d; @@ -151,12 +151,12 @@ // Create a JSON object only if one does not already exist. We create the // methods in a closure to avoid creating global variables. -if (typeof JSON !== 'object') { +if (typeof JSON !== "object") { JSON = {}; } (function () { - 'use strict'; + "use strict"; var rx_one = /^[\],:{}\s]*$/; var rx_two = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g; @@ -168,7 +168,7 @@ if (typeof JSON !== 'object') { function f(n) { // Format integers to have at least two digits. return n < 10 - ? '0' + n + ? "0" + n : n; } @@ -176,17 +176,17 @@ if (typeof JSON !== 'object') { return this.valueOf(); } - if (typeof Date.prototype.toJSON !== 'function') { + if (typeof Date.prototype.toJSON !== "function") { Date.prototype.toJSON = function () { return isFinite(this.valueOf()) - ? this.getUTCFullYear() + '-' + - f(this.getUTCMonth() + 1) + '-' + - f(this.getUTCDate()) + 'T' + - f(this.getUTCHours()) + ':' + - f(this.getUTCMinutes()) + ':' + - f(this.getUTCSeconds()) + 'Z' + ? this.getUTCFullYear() + "-" + + f(this.getUTCMonth() + 1) + "-" + + f(this.getUTCDate()) + "T" + + f(this.getUTCHours()) + ":" + + f(this.getUTCMinutes()) + ":" + + f(this.getUTCSeconds()) + "Z" : null; }; @@ -210,13 +210,13 @@ if (typeof JSON !== 'object') { rx_escapable.lastIndex = 0; return rx_escapable.test(string) - ? '"' + string.replace(rx_escapable, function (a) { + ? "\"" + string.replace(rx_escapable, function (a) { var c = meta[a]; - return typeof c === 'string' + return typeof c === "string" ? c - : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); - }) + '"' - : '"' + string + '"'; + : "\\u" + ("0000" + a.charCodeAt(0).toString(16)).slice(-4); + }) + "\"" + : "\"" + string + "\""; } @@ -234,51 +234,51 @@ if (typeof JSON !== 'object') { // If the value has a toJSON method, call it to obtain a replacement value. - if (value && typeof value === 'object' && - typeof value.toJSON === 'function') { + if (value && typeof value === "object" && + typeof value.toJSON === "function") { value = value.toJSON(key); } // If we were called with a replacer function, then call the replacer to // obtain a replacement value. - if (typeof rep === 'function') { + if (typeof rep === "function") { value = rep.call(holder, key, value); } // What happens next depends on the value's type. switch (typeof value) { - case 'string': + case "string": return quote(value); - case 'number': + case "number": // JSON numbers must be finite. Encode non-finite numbers as null. return isFinite(value) ? String(value) - : 'null'; + : "null"; - case 'boolean': - case 'null': + case "boolean": + case "null": // If the value is a boolean or null, convert it to a string. Note: -// typeof null does not produce 'null'. The case is included here in +// typeof null does not produce "null". The case is included here in // the remote chance that this gets fixed someday. return String(value); -// If the type is 'object', we might be dealing with an object or an array or +// If the type is "object", we might be dealing with an object or an array or // null. - case 'object': + case "object": -// Due to a specification blunder in ECMAScript, typeof null is 'object', +// Due to a specification blunder in ECMAScript, typeof null is "object", // so watch out for that case. if (!value) { - return 'null'; + return "null"; } // Make an array to hold the partial results of stringifying this object value. @@ -288,41 +288,41 @@ if (typeof JSON !== 'object') { // Is the value an array? - if (Object.prototype.toString.apply(value) === '[object Array]') { + if (Object.prototype.toString.apply(value) === "[object Array]") { // The value is an array. Stringify every element. Use null as a placeholder // for non-JSON values. length = value.length; for (i = 0; i < length; i += 1) { - partial[i] = str(i, value) || 'null'; + partial[i] = str(i, value) || "null"; } // Join all of the elements together, separated with commas, and wrap them in // brackets. v = partial.length === 0 - ? '[]' + ? "[]" : gap - ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' - : '[' + partial.join(',') + ']'; + ? "[\n" + gap + partial.join(",\n" + gap) + "\n" + mind + "]" + : "[" + partial.join(",") + "]"; gap = mind; return v; } // If the replacer is an array, use it to select the members to be stringified. - if (rep && typeof rep === 'object') { + if (rep && typeof rep === "object") { length = rep.length; for (i = 0; i < length; i += 1) { - if (typeof rep[i] === 'string') { + if (typeof rep[i] === "string") { k = rep[i]; v = str(k, value); if (v) { partial.push(quote(k) + ( gap - ? ': ' - : ':' + ? ": " + : ":" ) + v); } } @@ -337,8 +337,8 @@ if (typeof JSON !== 'object') { if (v) { partial.push(quote(k) + ( gap - ? ': ' - : ':' + ? ": " + : ":" ) + v); } } @@ -349,10 +349,10 @@ if (typeof JSON !== 'object') { // and wrap them in braces. v = partial.length === 0 - ? '{}' + ? "{}" : gap - ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' - : '{' + partial.join(',') + '}'; + ? "{\n" + gap + partial.join(",\n" + gap) + "\n" + mind + "}" + : "{" + partial.join(",") + "}"; gap = mind; return v; } @@ -360,15 +360,15 @@ if (typeof JSON !== 'object') { // If the JSON object does not yet have a stringify method, give it one. - if (typeof JSON.stringify !== 'function') { + if (typeof JSON.stringify !== "function") { meta = { // table of character substitutions - '\b': '\\b', - '\t': '\\t', - '\n': '\\n', - '\f': '\\f', - '\r': '\\r', - '"': '\\"', - '\\': '\\\\' + "\b": "\\b", + "\t": "\\t", + "\n": "\\n", + "\f": "\\f", + "\r": "\\r", + "\"": "\\\"", + "\\": "\\\\" }; JSON.stringify = function (value, replacer, space) { @@ -379,20 +379,20 @@ if (typeof JSON !== 'object') { // produce text that is more easily readable. var i; - gap = ''; - indent = ''; + gap = ""; + indent = ""; // If the space parameter is a number, make an indent string containing that // many spaces. - if (typeof space === 'number') { + if (typeof space === "number") { for (i = 0; i < space; i += 1) { - indent += ' '; + indent += " "; } // If the space parameter is a string, it will be used as the indent string. - } else if (typeof space === 'string') { + } else if (typeof space === "string") { indent = space; } @@ -400,23 +400,23 @@ if (typeof JSON !== 'object') { // Otherwise, throw an error. rep = replacer; - if (replacer && typeof replacer !== 'function' && - (typeof replacer !== 'object' || - typeof replacer.length !== 'number')) { - throw new Error('JSON.stringify'); + if (replacer && typeof replacer !== "function" && + (typeof replacer !== "object" || + typeof replacer.length !== "number")) { + throw new Error("JSON.stringify"); } -// Make a fake root object containing our value under the key of ''. +// Make a fake root object containing our value under the key of "". // Return the result of stringifying the value. - return str('', {'': value}); + return str("", {"": value}); }; } // If the JSON object does not yet have a parse method, give it one. - if (typeof JSON.parse !== 'function') { + if (typeof JSON.parse !== "function") { JSON.parse = function (text, reviver) { // The parse method takes a text and an optional reviver function, and returns @@ -432,7 +432,7 @@ if (typeof JSON !== 'object') { var k; var v; var value = holder[key]; - if (value && typeof value === 'object') { + if (value && typeof value === "object") { for (k in value) { if (Object.prototype.hasOwnProperty.call(value, k)) { v = walk(value, k); @@ -456,51 +456,51 @@ if (typeof JSON !== 'object') { rx_dangerous.lastIndex = 0; if (rx_dangerous.test(text)) { text = text.replace(rx_dangerous, function (a) { - return '\\u' + - ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + return "\\u" + + ("0000" + a.charCodeAt(0).toString(16)).slice(-4); }); } // In the second stage, we run the text against regular expressions that look -// for non-JSON patterns. We are especially concerned with '()' and 'new' -// because they can cause invocation, and '=' because it can cause mutation. +// for non-JSON patterns. We are especially concerned with "()" and "new" +// because they can cause invocation, and "=" because it can cause mutation. // But just to be safe, we want to reject all unexpected forms. // We split the second stage into 4 regexp operations in order to work around // crippling inefficiencies in IE's and Safari's regexp engines. First we -// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we -// replace all simple value tokens with ']' characters. Third, we delete all +// replace the JSON backslash pairs with "@" (a non-JSON character). Second, we +// replace all simple value tokens with "]" characters. Third, we delete all // open brackets that follow a colon or comma or that begin the text. Finally, -// we look to see that the remaining characters are only whitespace or ']' or -// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. +// we look to see that the remaining characters are only whitespace or "]" or +// "," or ":" or "{" or "}". If that is so, then the text is safe for eval. if ( rx_one.test( text - .replace(rx_two, '@') - .replace(rx_three, ']') - .replace(rx_four, '') + .replace(rx_two, "@") + .replace(rx_three, "]") + .replace(rx_four, "") ) ) { // In the third stage we use the eval function to compile the text into a -// JavaScript structure. The '{' operator is subject to a syntactic ambiguity +// JavaScript structure. The "{" operator is subject to a syntactic ambiguity // in JavaScript: it can begin a block or an object literal. We wrap the text // in parens to eliminate the ambiguity. - j = eval('(' + text + ')'); + j = eval("(" + text + ")"); // In the optional fourth stage, we recursively walk the new structure, passing // each name/value pair to a reviver function for possible transformation. - return typeof reviver === 'function' - ? walk({'': j}, '') + return (typeof reviver === "function") + ? walk({"": j}, "") : j; } // If the text is not JSON parseable, then a SyntaxError is thrown. - throw new SyntaxError('JSON.parse'); + throw new SyntaxError("JSON.parse"); }; } }()); diff --git a/json_parse.js b/json_parse.js index 359015a..6fcb796 100644 --- a/json_parse.js +++ b/json_parse.js @@ -1,6 +1,6 @@ /* json_parse.js - 2015-05-02 + 2016-05-02 Public Domain. @@ -25,7 +25,7 @@ myData = json_parse(text, function (key, value) { var a; - if (typeof value === 'string') { + if (typeof value === "string") { a = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); if (a) { @@ -67,14 +67,14 @@ var json_parse = (function () { 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' + "\"": "\"", + "\\": "\\", + "/": "/", + b: "\b", + f: "\f", + n: "\n", + r: "\r", + t: "\t" }; var text; @@ -83,7 +83,7 @@ var json_parse = (function () { // Call error when something is wrong. throw { - name: 'SyntaxError', + name: "SyntaxError", message: m, at: at, text: text @@ -110,40 +110,40 @@ var json_parse = (function () { // Parse a number value. - var number; - var string = ''; + var value; + var string = ""; - if (ch === '-') { - string = '-'; - next('-'); + if (ch === "-") { + string = "-"; + next("-"); } - while (ch >= '0' && ch <= '9') { + while (ch >= "0" && ch <= "9") { string += ch; next(); } - if (ch === '.') { - string += '.'; - while (next() && ch >= '0' && ch <= '9') { + if (ch === ".") { + string += "."; + while (next() && ch >= "0" && ch <= "9") { string += ch; } } - if (ch === 'e' || ch === 'E') { + if (ch === "e" || ch === "E") { string += ch; next(); - if (ch === '-' || ch === '+') { + if (ch === "-" || ch === "+") { string += ch; next(); } - while (ch >= '0' && ch <= '9') { + while (ch >= "0" && ch <= "9") { string += ch; next(); } } - number = +string; - if (!isFinite(number)) { + value = +string; + if (!isFinite(value)) { error("Bad number"); } else { - return number; + return value; } }; @@ -153,20 +153,20 @@ var json_parse = (function () { var hex; var i; - var string = ''; + var value = ""; var uffff; // When parsing for string values, we must look for " and \ characters. - if (ch === '"') { + if (ch === "\"") { while (next()) { - if (ch === '"') { + if (ch === "\"") { next(); - return string; + return value; } - if (ch === '\\') { + if (ch === "\\") { next(); - if (ch === 'u') { + if (ch === "u") { uffff = 0; for (i = 0; i < 4; i += 1) { hex = parseInt(next(), 16); @@ -175,14 +175,14 @@ var json_parse = (function () { } uffff = uffff * 16 + hex; } - string += String.fromCharCode(uffff); - } else if (typeof escapee[ch] === 'string') { - string += escapee[ch]; + value += String.fromCharCode(uffff); + } else if (typeof escapee[ch] === "string") { + value += escapee[ch]; } else { break; } } else { - string += ch; + value += ch; } } } @@ -193,7 +193,7 @@ var json_parse = (function () { // Skip whitespace. - while (ch && ch <= ' ') { + while (ch && ch <= " ") { next(); } }; @@ -203,24 +203,24 @@ var json_parse = (function () { // true, false, or null. switch (ch) { - case 't': - next('t'); - next('r'); - next('u'); - next('e'); + case "t": + next("t"); + next("r"); + next("u"); + next("e"); return true; - case 'f': - next('f'); - next('a'); - next('l'); - next('s'); - next('e'); + case "f": + next("f"); + next("a"); + next("l"); + next("s"); + next("e"); return false; - case 'n': - next('n'); - next('u'); - next('l'); - next('l'); + case "n": + next("n"); + next("u"); + next("l"); + next("l"); return null; } error("Unexpected '" + ch + "'"); @@ -232,23 +232,23 @@ var json_parse = (function () { // Parse an array value. - var array = []; + var arr = []; - if (ch === '[') { - next('['); + if (ch === "[") { + next("["); white(); - if (ch === ']') { - next(']'); - return array; // empty array + if (ch === "]") { + next("]"); + return arr; // empty array } while (ch) { - array.push(value()); + arr.push(value()); white(); - if (ch === ']') { - next(']'); - return array; + if (ch === "]") { + next("]"); + return arr; } - next(','); + next(","); white(); } } @@ -260,29 +260,29 @@ var json_parse = (function () { // Parse an object value. var key; - var object = {}; + var obj = {}; - if (ch === '{') { - next('{'); + if (ch === "{") { + next("{"); white(); - if (ch === '}') { - next('}'); - return object; // empty object + if (ch === "}") { + next("}"); + return obj; // empty object } while (ch) { key = string(); white(); - next(':'); - if (Object.hasOwnProperty.call(object, key)) { - error('Duplicate key "' + key + '"'); + next(":"); + if (Object.hasOwnProperty.call(obj, key)) { + error("Duplicate key '" + key + "'"); } - object[key] = value(); + obj[key] = value(); white(); - if (ch === '}') { - next('}'); - return object; + if (ch === "}") { + next("}"); + return obj; } - next(','); + next(","); white(); } } @@ -296,16 +296,16 @@ var json_parse = (function () { white(); switch (ch) { - case '{': + case "{": return object(); - case '[': + case "[": return array(); - case '"': + case "\"": return string(); - case '-': + case "-": return number(); default: - return (ch >= '0' && ch <= '9') + return (ch >= "0" && ch <= "9") ? number() : word(); } @@ -319,7 +319,7 @@ var json_parse = (function () { text = source; at = 0; - ch = ' '; + ch = " "; result = value(); white(); if (ch) { @@ -332,25 +332,25 @@ var json_parse = (function () { // in an empty key. If there is not a reviver function, we simply return the // result. - return (typeof reviver === 'function') + return (typeof reviver === "function") ? (function walk(holder, key) { var k; var v; - var value = holder[key]; - if (value && typeof value === 'object') { - for (k in value) { - if (Object.prototype.hasOwnProperty.call(value, k)) { - v = walk(value, k); + var val = holder[key]; + if (val && typeof val === "object") { + for (k in val) { + if (Object.prototype.hasOwnProperty.call(val, k)) { + v = walk(val, k); if (v !== undefined) { - value[k] = v; + val[k] = v; } else { - delete value[k]; + delete val[k]; } } } } - return reviver.call(holder, key, value); - }({'': result}, '')) + return reviver.call(holder, key, val); + }({"": result}, "")) : result; }; }()); diff --git a/json_parse_state.js b/json_parse_state.js index beb0f97..5241e45 100644 --- a/json_parse_state.js +++ b/json_parse_state.js @@ -1,6 +1,6 @@ /* json_parse_state.js - 2015-05-02 + 2016-05-02 Public Domain. @@ -25,7 +25,7 @@ myData = json_parse(text, function (key, value) { var a; - if (typeof value === 'string') { + if (typeof value === "string") { a = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); if (a) { @@ -61,7 +61,7 @@ var json_parse = (function () { // This function creates a JSON parse function that uses a state machine rather // than the dangerous eval function to parse a JSON text. - var state, // The state of the parser, one of + var state; // The state of the parser, one of // 'go' The starting state // 'ok' The final, accepting state // 'firstokey' Ready for the first key of the object or @@ -74,214 +74,214 @@ var json_parse = (function () { // an empty array // 'avalue' Ready for the next value of an array // 'acomma' Ready for a comma or closing ] - stack, // The stack, for controlling nesting. - container, // The current container object or array - key, // The current key - value, // The current value - escapes = { // Escapement translation table - '\\': '\\', - '"': '"', - '/': '/', - 't': '\t', - 'n': '\n', - 'r': '\r', - 'f': '\f', - 'b': '\b' + var stack; // The stack, for controlling nesting. + var container; // The current container object or array + var key; // The current key + var value; // The current value + var escapes = { // Escapement translation table + "\\": "\\", + "\"": "\"", + "/": "/", + "t": "\t", + "n": "\n", + "r": "\r", + "f": "\f", + "b": "\b" + }; + var string = { // The actions for string tokens + go: function () { + state = "ok"; + }, + firstokey: function () { + key = value; + state = "colon"; + }, + okey: function () { + key = value; + state = "colon"; + }, + ovalue: function () { + state = "ocomma"; + }, + firstavalue: function () { + state = "acomma"; + }, + avalue: function () { + state = "acomma"; + } + }; + var number = { // The actions for number tokens + go: function () { + state = "ok"; + }, + ovalue: function () { + state = "ocomma"; }, - string = { // The actions for string tokens + firstavalue: function () { + state = "acomma"; + }, + avalue: function () { + state = "acomma"; + } + }; + var action = { + +// The action table describes the behavior of the machine. It contains an +// object for each token. Each object contains a method that is called when +// a token is matched in a state. An object will lack a method for illegal +// states. + + "{": { go: function () { - state = 'ok'; - }, - firstokey: function () { - key = value; - state = 'colon'; - }, - okey: function () { - key = value; - state = 'colon'; + stack.push({state: "ok"}); + container = {}; + state = "firstokey"; }, ovalue: function () { - state = 'ocomma'; + stack.push({container: container, state: "ocomma", key: key}); + container = {}; + state = "firstokey"; }, firstavalue: function () { - state = 'acomma'; + stack.push({container: container, state: "acomma"}); + container = {}; + state = "firstokey"; }, avalue: function () { - state = 'acomma'; + stack.push({container: container, state: "acomma"}); + container = {}; + state = "firstokey"; } }, - number = { // The actions for number tokens + "}": { + firstokey: function () { + var pop = stack.pop(); + value = container; + container = pop.container; + key = pop.key; + state = pop.state; + }, + ocomma: function () { + var pop = stack.pop(); + container[key] = value; + value = container; + container = pop.container; + key = pop.key; + state = pop.state; + } + }, + "[": { go: function () { - state = 'ok'; + stack.push({state: "ok"}); + container = []; + state = "firstavalue"; }, ovalue: function () { - state = 'ocomma'; + stack.push({container: container, state: "ocomma", key: key}); + container = []; + state = "firstavalue"; }, firstavalue: function () { - state = 'acomma'; + stack.push({container: container, state: "acomma"}); + container = []; + state = "firstavalue"; }, avalue: function () { - state = 'acomma'; + stack.push({container: container, state: "acomma"}); + container = []; + state = "firstavalue"; } }, - action = { - -// The action table describes the behavior of the machine. It contains an -// object for each token. Each object contains a method that is called when -// a token is matched in a state. An object will lack a method for illegal -// states. - - '{': { - go: function () { - stack.push({state: 'ok'}); - container = {}; - state = 'firstokey'; - }, - ovalue: function () { - stack.push({container: container, state: 'ocomma', key: key}); - container = {}; - state = 'firstokey'; - }, - firstavalue: function () { - stack.push({container: container, state: 'acomma'}); - container = {}; - state = 'firstokey'; - }, - avalue: function () { - stack.push({container: container, state: 'acomma'}); - container = {}; - state = 'firstokey'; - } + "]": { + firstavalue: function () { + var pop = stack.pop(); + value = container; + container = pop.container; + key = pop.key; + state = pop.state; }, - '}': { - firstokey: function () { - var pop = stack.pop(); - value = container; - container = pop.container; - key = pop.key; - state = pop.state; - }, - ocomma: function () { - var pop = stack.pop(); - container[key] = value; - value = container; - container = pop.container; - key = pop.key; - state = pop.state; + acomma: function () { + var pop = stack.pop(); + container.push(value); + value = container; + container = pop.container; + key = pop.key; + state = pop.state; + } + }, + ":": { + colon: function () { + if (Object.hasOwnProperty.call(container, key)) { + throw new SyntaxError("Duplicate key '" + key + "\""); } + state = "ovalue"; + } + }, + ",": { + ocomma: function () { + container[key] = value; + state = "okey"; }, - '[': { - go: function () { - stack.push({state: 'ok'}); - container = []; - state = 'firstavalue'; - }, - ovalue: function () { - stack.push({container: container, state: 'ocomma', key: key}); - container = []; - state = 'firstavalue'; - }, - firstavalue: function () { - stack.push({container: container, state: 'acomma'}); - container = []; - state = 'firstavalue'; - }, - avalue: function () { - stack.push({container: container, state: 'acomma'}); - container = []; - state = 'firstavalue'; - } + acomma: function () { + container.push(value); + state = "avalue"; + } + }, + "true": { + go: function () { + value = true; + state = "ok"; }, - ']': { - firstavalue: function () { - var pop = stack.pop(); - value = container; - container = pop.container; - key = pop.key; - state = pop.state; - }, - acomma: function () { - var pop = stack.pop(); - container.push(value); - value = container; - container = pop.container; - key = pop.key; - state = pop.state; - } + ovalue: function () { + value = true; + state = "ocomma"; }, - ':': { - colon: function () { - if (Object.hasOwnProperty.call(container, key)) { - throw new SyntaxError('Duplicate key "' + key + '"'); - } - state = 'ovalue'; - } + firstavalue: function () { + value = true; + state = "acomma"; }, - ',': { - ocomma: function () { - container[key] = value; - state = 'okey'; - }, - acomma: function () { - container.push(value); - state = 'avalue'; - } + avalue: function () { + value = true; + state = "acomma"; + } + }, + "false": { + go: function () { + value = false; + state = "ok"; }, - 'true': { - go: function () { - value = true; - state = 'ok'; - }, - ovalue: function () { - value = true; - state = 'ocomma'; - }, - firstavalue: function () { - value = true; - state = 'acomma'; - }, - avalue: function () { - value = true; - state = 'acomma'; - } + ovalue: function () { + value = false; + state = "ocomma"; }, - 'false': { - go: function () { - value = false; - state = 'ok'; - }, - ovalue: function () { - value = false; - state = 'ocomma'; - }, - firstavalue: function () { - value = false; - state = 'acomma'; - }, - avalue: function () { - value = false; - state = 'acomma'; - } + firstavalue: function () { + value = false; + state = "acomma"; }, - 'null': { - go: function () { - value = null; - state = 'ok'; - }, - ovalue: function () { - value = null; - state = 'ocomma'; - }, - firstavalue: function () { - value = null; - state = 'acomma'; - }, - avalue: function () { - value = null; - state = 'acomma'; - } + avalue: function () { + value = false; + state = "acomma"; } - }; + }, + "null": { + go: function () { + value = null; + state = "ok"; + }, + ovalue: function () { + value = null; + state = "ocomma"; + }, + firstavalue: function () { + value = null; + state = "acomma"; + }, + avalue: function () { + value = null; + state = "acomma"; + } + } + }; function debackslashify(text) { @@ -299,12 +299,12 @@ var json_parse = (function () { // A regular expression is used to extract tokens from the JSON text. // The extraction process is cautious. - var result, - tx = /^[\u0020\t\n\r]*(?:([,:\[\]{}]|true|false|null)|(-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)|"((?:[^\r\n\t\\\"]|\\(?:["\\\/trnfb]|u[0-9a-fA-F]{4}))*)")/; + var result; + var tx = /^[\u0020\t\n\r]*(?:([,:\[\]{}]|true|false|null)|(-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)|"((?:[^\r\n\t\\\"]|\\(?:["\\\/trnfb]|u[0-9a-fA-F]{4}))*)")/; // Set the starting state. - state = 'go'; + state = "go"; // The stack records the container, key, and state for each object or array // that contains another object or array while processing nested structures. @@ -365,14 +365,14 @@ var json_parse = (function () { state = e; } -// The parsing is finished. If we are not in the final 'ok' state, or if the +// The parsing is finished. If we are not in the final "ok" state, or if the // remaining source contains anything except whitespace, then we did not have //a well-formed JSON text. - if (state !== 'ok' || (/[^\u0020\t\n\r]/.test(source))) { - throw state instanceof SyntaxError + if (state !== "ok" || (/[^\u0020\t\n\r]/.test(source))) { + throw (state instanceof SyntaxError) ? state - : new SyntaxError('JSON'); + : new SyntaxError("JSON"); } // If there is a reviver function, we recursively walk the new structure, @@ -381,23 +381,25 @@ var json_parse = (function () { // value in an empty key. If there is not a reviver function, we simply return // that value. - return typeof reviver === 'function' + return (typeof reviver === "function") ? (function walk(holder, key) { - var k, v, value = holder[key]; - if (value && typeof value === 'object') { + var k; + var v; + var val = holder[key]; + if (val && typeof val === "object") { for (k in value) { - if (Object.prototype.hasOwnProperty.call(value, k)) { - v = walk(value, k); + if (Object.prototype.hasOwnProperty.call(val, k)) { + v = walk(val, k); if (v !== undefined) { - value[k] = v; + val[k] = v; } else { - delete value[k]; + delete val[k]; } } } } - return reviver.call(holder, key, value); - }({'': value}, '')) + return reviver.call(holder, key, val); + }({"": value}, "")) : value; }; }()); From 031b1d9e6971bd4c433ca85e216cc853f5a867bd Mon Sep 17 00:00:00 2001 From: Douglas Crockford Date: Fri, 28 Oct 2016 06:05:19 -0700 Subject: [PATCH 16/28] is --- cycle.js | 6 +++--- json2.js | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cycle.js b/cycle.js index 7e00c5c..fae2999 100644 --- a/cycle.js +++ b/cycle.js @@ -16,8 +16,8 @@ /*jslint eval, for */ /*property - $ref, decycle, forEach, isArray, keys, length, push, retrocycle, stringify, - test + $ref, decycle, forEach, indexOf, isArray, keys, length, push, retrocycle, + stringify, test */ if (typeof JSON.decycle !== "function") { @@ -141,7 +141,7 @@ if (typeof JSON.retrocycle !== "function") { // return JSON.retrocycle(JSON.parse(s)); // produces an array containing a single element which is the array itself. - var px = /^\$(?:\[(?:\d+|\"(?:[^\\\"\u0000-\u001f]|\\([\\\"\/bfnrt]|u[0-9a-zA-Z]{4}))*\")\])*$/; + var px = /^\$(?:\[(?:\d+|"(?:[^\\"\u0000-\u001f]|\\([\\"\/bfnrt]|u[0-9a-zA-Z]{4}))*")\])*$/; (function rez(value) { diff --git a/json2.js b/json2.js index d4720c0..e66f817 100644 --- a/json2.js +++ b/json2.js @@ -1,5 +1,5 @@ // json2.js -// 2016-05-01 +// 2016-10-28 // Public Domain. // NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. // See http://www.JSON.org/js.html @@ -10,7 +10,7 @@ // NOT CONTROL. // This file creates a global JSON object containing two methods: stringify -// and parse. This file is provides the ES5 JSON capability to ES3 systems. +// and parse. This file provides the ES5 JSON capability to ES3 systems. // If a project might run on IE8 or earlier, then this file should be included. // This file does nothing on ES5 systems. @@ -162,7 +162,7 @@ if (typeof JSON !== "object") { var rx_two = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g; var rx_three = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g; var rx_four = /(?:^|:|,)(?:\s*\[)+/g; - var rx_escapable = /[\\\"\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; + var rx_escapable = /[\\"\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; var rx_dangerous = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; function f(n) { From e25ba46913160617935f837836016ea7ed2be08c Mon Sep 17 00:00:00 2001 From: Douglas Crockford Date: Wed, 8 Feb 2017 10:28:50 -0800 Subject: [PATCH 17/28] WeakMap --- cycle.js | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/cycle.js b/cycle.js index fae2999..0c7904f 100644 --- a/cycle.js +++ b/cycle.js @@ -1,6 +1,6 @@ /* cycle.js - 2016-05-01 + 2017-02-07 Public Domain. @@ -13,11 +13,13 @@ NOT CONTROL. */ -/*jslint eval, for */ +// The file uses the WeakMap feature of ES6. + +/*jslint es6, eval */ /*property - $ref, decycle, forEach, indexOf, isArray, keys, length, push, retrocycle, - stringify, test + $ref, decycle, forEach, get, indexOf, isArray, keys, length, push, + retrocycle, set, stringify, test */ if (typeof JSON.decycle !== "function") { @@ -48,14 +50,13 @@ if (typeof JSON.decycle !== "function") { // the object or array. [NUMBER] or [STRING] indicates a child element or // property. - var objects = []; // Keep a reference to each unique object or array - var paths = []; // Keep the path to each unique object or array + var objects = new WeakMap(); // object to path mappings return (function derez(value, path) { // The derez function recurses through the object, producing the deep copy. - var i; // The loop counter + var old_path; // The path of an earlier occurance of value var nu; // The new object or array // If a replacer function was provided, then call it to get a replacement value. @@ -77,19 +78,17 @@ if (typeof JSON.decycle !== "function") { ) { // If the value is an object or array, look to see if we have already -// encountered it. If so, return a {"$ref":PATH} object. This is a hard -// linear search that will get slower as the number of unique objects grows. -// Someday, this should be replaced with an ES6 WeakMap. +// encountered it. If so, return a {"$ref":PATH} object. This uses an +// ES6 WeakMap. - i = objects.indexOf(value); - if (i >= 0) { - return {$ref: paths[i]}; + old_path = objects.get(value); + if (old_path !== undefined) { + return {$ref: old_path}; } // Otherwise, accumulate the unique value and its path. - objects.push(value); - paths.push(path); + objects.set(value, path); // If it is an array, replicate the array. From 2a76286e00cdc1e98fbc9e9ec6589563a3a4c3bb Mon Sep 17 00:00:00 2001 From: Douglas Crockford Date: Sat, 21 Apr 2018 16:22:47 -0700 Subject: [PATCH 18/28] Remove urls --- json2.js | 94 +++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 59 insertions(+), 35 deletions(-) diff --git a/json2.js b/json2.js index e66f817..f6fada6 100644 --- a/json2.js +++ b/json2.js @@ -1,10 +1,7 @@ // json2.js -// 2016-10-28 +// 2017-06-12 // Public Domain. // NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. -// See http://www.JSON.org/js.html -// This code should be minified before deployment. -// See http://javascript.crockford.com/jsmin.html // USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO // NOT CONTROL. @@ -113,25 +110,31 @@ // a = // /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); // if (a) { -// return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], -// +a[5], +a[6])); +// return new Date(Date.UTC( +// +a[1], +a[2] - 1, +a[3], +a[4], +a[5], +a[6] +// )); // } +// return value; // } -// return value; // }); -// myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { -// var d; -// if (typeof value === "string" && -// value.slice(0, 5) === "Date(" && -// value.slice(-1) === ")") { -// d = new Date(value.slice(5, -1)); -// if (d) { -// return d; +// myData = JSON.parse( +// "[\"Date(09/09/2001)\"]", +// function (key, value) { +// var d; +// if ( +// typeof value === "string" +// && value.slice(0, 5) === "Date(" +// && value.slice(-1) === ")" +// ) { +// d = new Date(value.slice(5, -1)); +// if (d) { +// return d; +// } // } +// return value; // } -// return value; -// }); +// ); // This is a reference implementation. You are free to copy, modify, or // redistribute. @@ -167,7 +170,7 @@ if (typeof JSON !== "object") { function f(n) { // Format integers to have at least two digits. - return n < 10 + return (n < 10) ? "0" + n : n; } @@ -181,12 +184,20 @@ if (typeof JSON !== "object") { Date.prototype.toJSON = function () { return isFinite(this.valueOf()) - ? this.getUTCFullYear() + "-" + - f(this.getUTCMonth() + 1) + "-" + - f(this.getUTCDate()) + "T" + - f(this.getUTCHours()) + ":" + - f(this.getUTCMinutes()) + ":" + - f(this.getUTCSeconds()) + "Z" + ? ( + this.getUTCFullYear() + + "-" + + f(this.getUTCMonth() + 1) + + "-" + + f(this.getUTCDate()) + + "T" + + f(this.getUTCHours()) + + ":" + + f(this.getUTCMinutes()) + + ":" + + f(this.getUTCSeconds()) + + "Z" + ) : null; }; @@ -234,8 +245,11 @@ if (typeof JSON !== "object") { // If the value has a toJSON method, call it to obtain a replacement value. - if (value && typeof value === "object" && - typeof value.toJSON === "function") { + if ( + value + && typeof value === "object" + && typeof value.toJSON === "function" + ) { value = value.toJSON(key); } @@ -256,7 +270,7 @@ if (typeof JSON !== "object") { // JSON numbers must be finite. Encode non-finite numbers as null. - return isFinite(value) + return (isFinite(value)) ? String(value) : "null"; @@ -304,7 +318,14 @@ if (typeof JSON !== "object") { v = partial.length === 0 ? "[]" : gap - ? "[\n" + gap + partial.join(",\n" + gap) + "\n" + mind + "]" + ? ( + "[\n" + + gap + + partial.join(",\n" + gap) + + "\n" + + mind + + "]" + ) : "[" + partial.join(",") + "]"; gap = mind; return v; @@ -320,7 +341,7 @@ if (typeof JSON !== "object") { v = str(k, value); if (v) { partial.push(quote(k) + ( - gap + (gap) ? ": " : ":" ) + v); @@ -336,7 +357,7 @@ if (typeof JSON !== "object") { v = str(k, value); if (v) { partial.push(quote(k) + ( - gap + (gap) ? ": " : ":" ) + v); @@ -400,9 +421,10 @@ if (typeof JSON !== "object") { // Otherwise, throw an error. rep = replacer; - if (replacer && typeof replacer !== "function" && - (typeof replacer !== "object" || - typeof replacer.length !== "number")) { + if (replacer && typeof replacer !== "function" && ( + typeof replacer !== "object" + || typeof replacer.length !== "number" + )) { throw new Error("JSON.stringify"); } @@ -456,8 +478,10 @@ if (typeof JSON !== "object") { rx_dangerous.lastIndex = 0; if (rx_dangerous.test(text)) { text = text.replace(rx_dangerous, function (a) { - return "\\u" + - ("0000" + a.charCodeAt(0).toString(16)).slice(-4); + return ( + "\\u" + + ("0000" + a.charCodeAt(0).toString(16)).slice(-4) + ); }); } From 58df70e97f9031c58361d38d59997485e7a693a2 Mon Sep 17 00:00:00 2001 From: Douglas Crockford Date: Sun, 29 Apr 2018 14:14:49 -0700 Subject: [PATCH 19/28] && --- cycle.js | 15 ++- jsonzip.js | 380 +++++++++++++++++++++++++++++++++++++++++++++++++++++ test.html | 36 +++++ 3 files changed, 424 insertions(+), 7 deletions(-) create mode 100644 jsonzip.js create mode 100644 test.html diff --git a/cycle.js b/cycle.js index 0c7904f..d23b380 100644 --- a/cycle.js +++ b/cycle.js @@ -1,6 +1,6 @@ /* cycle.js - 2017-02-07 + 2017-06-12 Public Domain. @@ -69,12 +69,13 @@ if (typeof JSON.decycle !== "function") { // one of the weird builtin objects. if ( - typeof value === "object" && value !== null && - !(value instanceof Boolean) && - !(value instanceof Date) && - !(value instanceof Number) && - !(value instanceof RegExp) && - !(value instanceof String) + typeof value === "object" + && value !== null + && !(value instanceof Boolean) + && !(value instanceof Date) + && !(value instanceof Number) + && !(value instanceof RegExp) + && !(value instanceof String) ) { // If the value is an object or array, look to see if we have already diff --git a/jsonzip.js b/jsonzip.js new file mode 100644 index 0000000..60be35c --- /dev/null +++ b/jsonzip.js @@ -0,0 +1,380 @@ +// jsonzip.js + +// 2014-05-20 + +// This will be a JavaScript implementation of JSONzip. + +var JSONzip = (function () { + 'use strict'; + +// Constants + + var bcd = [ + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', '-', '+', 'E' + ], + education = 1000000, + end = 256, // End of string code. + endOfNumber = bcd.length, + // The first positive integer that cannot be + int4 = 16, // encoded in 4 bits. + int7 = 144, // encoded in 7 bits. + int14 = 16528, // encoded in 14 bits. + // The value code for + zipEmptyObject = 0, // an empty object. + zipEmptyArray = 1, // an empty array. + zipTrue = 2, // true. + zipFalse = 3, // false. + zipNull = 4, // null. + zipObject = 5, // a non-empty object. + zipArrayString = 6, // an array with a string as its first element. + zipArrayValue = 7; // an array with other value as its first element. + + function huff(domain) { + +// The huff function produces a Huffman encoder/decoder object. + + var symbols = [], + table, + toLearn = 1000000, + upToDate = false, + width; + +// Make the leaf symbols. + + for (int i = 0; i < domain; i += 1) { + symbols[i] = { + integer: i + weight: 0 + }; + } + +// Make the links. + + for (int i = domain; i < length; i += 1) { + symbols[i] = { + weight: 0 + }; + } + + return { + generate: function () { + var avail, first, head, i, next, previous, second, symbol; + if (!upToDate) { + +// Phase One: Sort the symbols by weight into a linked list. + + head = symbols[0]; + previous = head; + table = undefined; + head.next = undefined; + for (i = 1; i < domain; i += 1) { + symbol = symbols[i]; + +// If this symbol weighs less than the head, then it becomes the new head. + + if (symbol.weight < head.weight) { + symbol.next = head; + head = symbol; + } else { + +// We will start the search from the previous symbol instead of the head unless +// the current symbol weights less than the previous symbol. + + if (symbol.weight < previous.weight) { + previous = head; + } + +// Find a connected pair (previous and next) where the symbol weighs the same +// or more than previous but less than the next. Link the symbol between them. + + while (true) { + next = previous.next; + if (next === undefined || symbol.weight < next.weight) { + break; + } + previous = next; + } + symbol.next = next; + previous.next = symbol; + previous = symbol; + } + } + +// Phase Two: Make new symbols from the two lightest symbols until only one +// symbol remains. The final symbol becomes the root of the table binary tree. + + avail = domain; + previous = head; + while (true) { + first = head; + second = first.next; + head = second.next; + symbol = symbols[avail]; + avail += 1; + symbol.weight = first.weight + second.weight; + symbol.zero = first; + symbol.one = second; + symbol.back = undefined; + first.back = symbol; + second.back = symbol; + if (head === undefined) { + break; + } + +// Insert the new symbol back into the sorted list. + + if (symbol.weight < head.weight) { + symbol.next = head; + head = symbol; + previous = head; + } else { + while (true) { + next = previous.next; + if (next === undefined || symbol.weight < next.weight) { + break; + } + previous = next; + } + symbol.next = next; + previous.next = symbol; + previous = symbol; + } + } + +// The last remaining symbol is the root of the table. + + table = symbol; + upToDate = true; + } + }, + read: function read(bitreader) { + var symbol = table; + width = 0; + while (symbol.integer === undefined) { + width += 1; + symbol = bitreader.bit() + ? symbol.one + : symbol.zero; + } + tick(symbol.integer); + return symbol.integer; + }, + tick: function tick(int) { + if (toLearn > 0) { + toLearn -= 1; + symbols[value].weight += 1; + upToDate = false; + } + }, + write: function write(value, bitwriter) { + width = 0; + (function writebit(symbol) { + var back = symbol.back; + if (back !== undefined) { + width += 1; + writebit(back); + if (back.zero === symbol) { + return bitwriter.zero(); + } + return bitwriter.one(); + } + }(symbols[value])); + tick(value); + } + }; + } + + function make_state() { + return { + namehuff: huff(end + 1), + namehuffext: huff(end + 1), + namekeep: keep(9), + stringhuff: huff(end + 1), + stringhuffext: huff(end + 1), + stringkeep: keep(11), + valuekeep: keep(10), + generate: function generate() { + this.namehuff.generate(); + this.namehuffext.generate(); + this.stringhuff.generate(); + this.stringhuffext.generate(); + } + }; + } + + return { + encoder: function (writer) { + var nr_bits = 0, // The number of bits available in unwritten + state = make_state(), + unwritten = 0, // The number of bits written so far + vacant = 0; // The unwritten byte + +// The encoder function returns a new encoder object. +// A writer is a function that takes a number (0..255) and delivers it to an +// output. + + function write(bits, width) { + +// Write bits:width to the writer, a function that accepts the next byte of +// output. + + var give; + if (bits === 0 && width === 0) { + return; + } + if (width <= 0 || width > 32) { + throw new TypeError("Bad read width " + width); + } + while (width > 0) { + give = Math.min(width, vacant); + unwritten |= ( + (bits >>> (width - give)) & ((1 << give) - 1) + ) << (vacant - give); + width -= give; + nrBits += give; + vacant -= give; + if (vacant == 0) { + writer(unwritten); + unwritten = 0; + vacant = 8; + } + } + } + + function one() { + return write(1, 1); + } + + function zero() { + return write(0, 1); + } + + function pad(width) { + var gap = nrBits % width, + padding; + if (gap < 0) { + gap += width; + } + if (gap != 0) { + padding = width - gap; + while (padding > 0) { + zero(); + padding -= 1; + } + } + } + + return { + flush: function () { + }, + pad: function (size) { + }, + zip: function (value) { + state.generate(); + } + }; + }, + decoder: function (reader) { + +// The decoder function returns a new decoder object. +// A reader is a function that returns the next byte from the input, +// or undefined if there is nothing left. + + var available = 0, // The number of bits available in unread + nr_bits = 0, // The number of bits read so far + state = make_state(), + unread = 0; // The unread byte + + function read(width) { + +// Read (width) bits from the reader, returning the bits as a small positive +// integer. + + var result = 0, + take; // The number of bits to take from the current byte + +// If no bits are requested, return zero. + + if (width === 0) { + return 0; + } + +// Make sure the width is reasonable. + + if (width < 0 || width > 32) { + throw new TypeError("Bad read width " + width); + } + +// Loop until the width is satisified. + + while (width > 0) { + +// If all of the available bits have been taken, read the next byte. + + if (available == 0) { + unread = reader(); + if (unread === undefined || unread === '') { + throw new TypeError("Attempt to read past end."); + } + if typeof unread === 'string') { + if (unread.length !== 1) { + throw new TypeError("Data size error."); + } + unread = unread.charCodeAt(0); + } + if (typeof unread !== 'number') { + throw new TypeError("Data type error."); + } + if (unread < 0) { + unread += 256; + } + if (unread < 0 || unread >= 256) { + throw new TypeError("Data size error."); + } + available = 8; + } + +// Take some bits from the current unread byte and combine them into the result. + + take = Math.min(width, available); + result |= ( + (unread >>> (available - take)) & ((1 << take) - 1) + ) << (width - take); + nr_bits += take; + available -= take; + width -= take; + } + return result; + } + + function bit() { + +// Read a bit, returning it as a boolean. + + return read(1) != 0; + } + + function pad(factor) { + var padding = factor - (nr_bits % factor), + result = true; + + while (padding > 0) { + if (bit()) { + result = false; + } + padding -= 1; + } + return result; + } + + return { + pad: function (size) { + }, + unzip: function () { + state.generate(); + } + }; + } + }; +}()); + diff --git a/test.html b/test.html new file mode 100644 index 0000000..26ca5a0 --- /dev/null +++ b/test.html @@ -0,0 +1,36 @@ + +

+
From 9139a9f6729f3c1623ca3ff5ccd58dec1523acab Mon Sep 17 00:00:00 2001 From: Douglas Crockford Date: Tue, 15 May 2018 08:18:51 -0700 Subject: [PATCH 20/28] ?: --- cycle.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cycle.js b/cycle.js index d23b380..5cc4547 100644 --- a/cycle.js +++ b/cycle.js @@ -1,6 +1,6 @@ /* cycle.js - 2017-06-12 + 2018-05-15 Public Domain. @@ -15,7 +15,7 @@ // The file uses the WeakMap feature of ES6. -/*jslint es6, eval */ +/*jslint eval */ /*property $ref, decycle, forEach, get, indexOf, isArray, keys, length, push, @@ -141,7 +141,7 @@ if (typeof JSON.retrocycle !== "function") { // return JSON.retrocycle(JSON.parse(s)); // produces an array containing a single element which is the array itself. - var px = /^\$(?:\[(?:\d+|"(?:[^\\"\u0000-\u001f]|\\([\\"\/bfnrt]|u[0-9a-zA-Z]{4}))*")\])*$/; + var px = /^\$(?:\[(?:\d+|"(?:[^\\"\u0000-\u001f]|\\(?:[\\"\/bfnrt]|u[0-9a-zA-Z]{4}))*")\])*$/; (function rez(value) { From 03157639c7a7cddd2e9f032537f346f1a87c0f6d Mon Sep 17 00:00:00 2001 From: Douglas Crockford Date: Mon, 23 Jul 2018 11:12:50 -0700 Subject: [PATCH 21/28] jsonzip --- jsonzip.js | 380 ----------------------------------------------------- 1 file changed, 380 deletions(-) delete mode 100644 jsonzip.js diff --git a/jsonzip.js b/jsonzip.js deleted file mode 100644 index 60be35c..0000000 --- a/jsonzip.js +++ /dev/null @@ -1,380 +0,0 @@ -// jsonzip.js - -// 2014-05-20 - -// This will be a JavaScript implementation of JSONzip. - -var JSONzip = (function () { - 'use strict'; - -// Constants - - var bcd = [ - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', '-', '+', 'E' - ], - education = 1000000, - end = 256, // End of string code. - endOfNumber = bcd.length, - // The first positive integer that cannot be - int4 = 16, // encoded in 4 bits. - int7 = 144, // encoded in 7 bits. - int14 = 16528, // encoded in 14 bits. - // The value code for - zipEmptyObject = 0, // an empty object. - zipEmptyArray = 1, // an empty array. - zipTrue = 2, // true. - zipFalse = 3, // false. - zipNull = 4, // null. - zipObject = 5, // a non-empty object. - zipArrayString = 6, // an array with a string as its first element. - zipArrayValue = 7; // an array with other value as its first element. - - function huff(domain) { - -// The huff function produces a Huffman encoder/decoder object. - - var symbols = [], - table, - toLearn = 1000000, - upToDate = false, - width; - -// Make the leaf symbols. - - for (int i = 0; i < domain; i += 1) { - symbols[i] = { - integer: i - weight: 0 - }; - } - -// Make the links. - - for (int i = domain; i < length; i += 1) { - symbols[i] = { - weight: 0 - }; - } - - return { - generate: function () { - var avail, first, head, i, next, previous, second, symbol; - if (!upToDate) { - -// Phase One: Sort the symbols by weight into a linked list. - - head = symbols[0]; - previous = head; - table = undefined; - head.next = undefined; - for (i = 1; i < domain; i += 1) { - symbol = symbols[i]; - -// If this symbol weighs less than the head, then it becomes the new head. - - if (symbol.weight < head.weight) { - symbol.next = head; - head = symbol; - } else { - -// We will start the search from the previous symbol instead of the head unless -// the current symbol weights less than the previous symbol. - - if (symbol.weight < previous.weight) { - previous = head; - } - -// Find a connected pair (previous and next) where the symbol weighs the same -// or more than previous but less than the next. Link the symbol between them. - - while (true) { - next = previous.next; - if (next === undefined || symbol.weight < next.weight) { - break; - } - previous = next; - } - symbol.next = next; - previous.next = symbol; - previous = symbol; - } - } - -// Phase Two: Make new symbols from the two lightest symbols until only one -// symbol remains. The final symbol becomes the root of the table binary tree. - - avail = domain; - previous = head; - while (true) { - first = head; - second = first.next; - head = second.next; - symbol = symbols[avail]; - avail += 1; - symbol.weight = first.weight + second.weight; - symbol.zero = first; - symbol.one = second; - symbol.back = undefined; - first.back = symbol; - second.back = symbol; - if (head === undefined) { - break; - } - -// Insert the new symbol back into the sorted list. - - if (symbol.weight < head.weight) { - symbol.next = head; - head = symbol; - previous = head; - } else { - while (true) { - next = previous.next; - if (next === undefined || symbol.weight < next.weight) { - break; - } - previous = next; - } - symbol.next = next; - previous.next = symbol; - previous = symbol; - } - } - -// The last remaining symbol is the root of the table. - - table = symbol; - upToDate = true; - } - }, - read: function read(bitreader) { - var symbol = table; - width = 0; - while (symbol.integer === undefined) { - width += 1; - symbol = bitreader.bit() - ? symbol.one - : symbol.zero; - } - tick(symbol.integer); - return symbol.integer; - }, - tick: function tick(int) { - if (toLearn > 0) { - toLearn -= 1; - symbols[value].weight += 1; - upToDate = false; - } - }, - write: function write(value, bitwriter) { - width = 0; - (function writebit(symbol) { - var back = symbol.back; - if (back !== undefined) { - width += 1; - writebit(back); - if (back.zero === symbol) { - return bitwriter.zero(); - } - return bitwriter.one(); - } - }(symbols[value])); - tick(value); - } - }; - } - - function make_state() { - return { - namehuff: huff(end + 1), - namehuffext: huff(end + 1), - namekeep: keep(9), - stringhuff: huff(end + 1), - stringhuffext: huff(end + 1), - stringkeep: keep(11), - valuekeep: keep(10), - generate: function generate() { - this.namehuff.generate(); - this.namehuffext.generate(); - this.stringhuff.generate(); - this.stringhuffext.generate(); - } - }; - } - - return { - encoder: function (writer) { - var nr_bits = 0, // The number of bits available in unwritten - state = make_state(), - unwritten = 0, // The number of bits written so far - vacant = 0; // The unwritten byte - -// The encoder function returns a new encoder object. -// A writer is a function that takes a number (0..255) and delivers it to an -// output. - - function write(bits, width) { - -// Write bits:width to the writer, a function that accepts the next byte of -// output. - - var give; - if (bits === 0 && width === 0) { - return; - } - if (width <= 0 || width > 32) { - throw new TypeError("Bad read width " + width); - } - while (width > 0) { - give = Math.min(width, vacant); - unwritten |= ( - (bits >>> (width - give)) & ((1 << give) - 1) - ) << (vacant - give); - width -= give; - nrBits += give; - vacant -= give; - if (vacant == 0) { - writer(unwritten); - unwritten = 0; - vacant = 8; - } - } - } - - function one() { - return write(1, 1); - } - - function zero() { - return write(0, 1); - } - - function pad(width) { - var gap = nrBits % width, - padding; - if (gap < 0) { - gap += width; - } - if (gap != 0) { - padding = width - gap; - while (padding > 0) { - zero(); - padding -= 1; - } - } - } - - return { - flush: function () { - }, - pad: function (size) { - }, - zip: function (value) { - state.generate(); - } - }; - }, - decoder: function (reader) { - -// The decoder function returns a new decoder object. -// A reader is a function that returns the next byte from the input, -// or undefined if there is nothing left. - - var available = 0, // The number of bits available in unread - nr_bits = 0, // The number of bits read so far - state = make_state(), - unread = 0; // The unread byte - - function read(width) { - -// Read (width) bits from the reader, returning the bits as a small positive -// integer. - - var result = 0, - take; // The number of bits to take from the current byte - -// If no bits are requested, return zero. - - if (width === 0) { - return 0; - } - -// Make sure the width is reasonable. - - if (width < 0 || width > 32) { - throw new TypeError("Bad read width " + width); - } - -// Loop until the width is satisified. - - while (width > 0) { - -// If all of the available bits have been taken, read the next byte. - - if (available == 0) { - unread = reader(); - if (unread === undefined || unread === '') { - throw new TypeError("Attempt to read past end."); - } - if typeof unread === 'string') { - if (unread.length !== 1) { - throw new TypeError("Data size error."); - } - unread = unread.charCodeAt(0); - } - if (typeof unread !== 'number') { - throw new TypeError("Data type error."); - } - if (unread < 0) { - unread += 256; - } - if (unread < 0 || unread >= 256) { - throw new TypeError("Data size error."); - } - available = 8; - } - -// Take some bits from the current unread byte and combine them into the result. - - take = Math.min(width, available); - result |= ( - (unread >>> (available - take)) & ((1 << take) - 1) - ) << (width - take); - nr_bits += take; - available -= take; - width -= take; - } - return result; - } - - function bit() { - -// Read a bit, returning it as a boolean. - - return read(1) != 0; - } - - function pad(factor) { - var padding = factor - (nr_bits % factor), - result = true; - - while (padding > 0) { - if (bit()) { - result = false; - } - padding -= 1; - } - return result; - } - - return { - pad: function (size) { - }, - unzip: function () { - state.generate(); - } - }; - } - }; -}()); - From d1e7d0bfd3cdba07762095245326f300dc53b5f2 Mon Sep 17 00:00:00 2001 From: Douglas Crockford Date: Tue, 28 May 2019 10:42:04 -0700 Subject: [PATCH 22/28] end of life --- json_parse.js | 356 -------------------------------------- json_parse_state.js | 405 -------------------------------------------- 2 files changed, 761 deletions(-) delete mode 100644 json_parse.js delete mode 100644 json_parse_state.js diff --git a/json_parse.js b/json_parse.js deleted file mode 100644 index 6fcb796..0000000 --- a/json_parse.js +++ /dev/null @@ -1,356 +0,0 @@ -/* - json_parse.js - 2016-05-02 - - Public Domain. - - NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. - - This file creates a json_parse function. - - json_parse(text, reviver) - This method parses a JSON text to produce an object or array. - It can throw a SyntaxError exception. - - The optional reviver parameter is a function that can filter and - transform the results. It receives each of the keys and values, - and its return value is used instead of the original value. - If it returns what it received, then the structure is not modified. - If it returns undefined then the member is deleted. - - Example: - - // Parse the text. Values that look like ISO date strings will - // be converted to Date objects. - - myData = json_parse(text, function (key, value) { - var a; - if (typeof value === "string") { - a = -/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); - if (a) { - return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], - +a[5], +a[6])); - } - } - return value; - }); - - This is a reference implementation. You are free to copy, modify, or - redistribute. - - This code should be minified before deployment. - See http://javascript.crockford.com/jsmin.html - - USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO - NOT CONTROL. -*/ - -/*jslint for */ - -/*property - at, b, call, charAt, f, fromCharCode, hasOwnProperty, message, n, name, - prototype, push, r, t, text -*/ - -var json_parse = (function () { - "use strict"; - -// This is a function that can parse a JSON text, producing a JavaScript -// data structure. It is a simple, recursive descent parser. It does not use -// eval or regular expressions, so it can be used as a model for implementing -// a JSON parser in other languages. - -// We are defining the function inside of another function to avoid creating -// global variables. - - 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; - - var error = function (m) { - -// Call error when something is wrong. - - throw { - name: "SyntaxError", - message: m, - at: at, - text: text - }; - }; - - var next = function (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; - }; - - var number = function () { - -// Parse a number value. - - var value; - var string = ""; - - if (ch === "-") { - string = "-"; - next("-"); - } - while (ch >= "0" && ch <= "9") { - string += ch; - next(); - } - if (ch === ".") { - string += "."; - while (next() && ch >= "0" && ch <= "9") { - string += ch; - } - } - if (ch === "e" || ch === "E") { - string += ch; - next(); - if (ch === "-" || ch === "+") { - string += ch; - next(); - } - while (ch >= "0" && ch <= "9") { - string += ch; - next(); - } - } - value = +string; - if (!isFinite(value)) { - error("Bad number"); - } else { - return value; - } - }; - - var string = function () { - -// Parse a string value. - - var hex; - var i; - var value = ""; - var uffff; - -// When parsing for string values, we must look for " and \ characters. - - if (ch === "\"") { - while (next()) { - if (ch === "\"") { - next(); - return value; - } - 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; - } - value += String.fromCharCode(uffff); - } else if (typeof escapee[ch] === "string") { - value += escapee[ch]; - } else { - break; - } - } else { - value += ch; - } - } - } - error("Bad string"); - }; - - var white = function () { - -// Skip whitespace. - - while (ch && ch <= " ") { - next(); - } - }; - - var word = function () { - -// true, false, or null. - - 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; - } - error("Unexpected '" + ch + "'"); - }; - - var value; // Place holder for the value function. - - var array = function () { - -// Parse an array value. - - var arr = []; - - if (ch === "[") { - next("["); - white(); - if (ch === "]") { - next("]"); - return arr; // empty array - } - while (ch) { - arr.push(value()); - white(); - if (ch === "]") { - next("]"); - return arr; - } - next(","); - white(); - } - } - error("Bad array"); - }; - - var object = function () { - -// Parse an object value. - - var key; - var obj = {}; - - if (ch === "{") { - next("{"); - white(); - if (ch === "}") { - next("}"); - return obj; // empty object - } - while (ch) { - key = string(); - white(); - next(":"); - if (Object.hasOwnProperty.call(obj, key)) { - error("Duplicate key '" + key + "'"); - } - obj[key] = value(); - white(); - if (ch === "}") { - next("}"); - return obj; - } - next(","); - white(); - } - } - error("Bad object"); - }; - - value = function () { - -// Parse a JSON value. It could be an object, an array, a string, a number, -// or a word. - - 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. - - return 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 val) { - if (Object.prototype.hasOwnProperty.call(val, k)) { - v = walk(val, k); - if (v !== undefined) { - val[k] = v; - } else { - delete val[k]; - } - } - } - } - return reviver.call(holder, key, val); - }({"": result}, "")) - : result; - }; -}()); diff --git a/json_parse_state.js b/json_parse_state.js deleted file mode 100644 index 5241e45..0000000 --- a/json_parse_state.js +++ /dev/null @@ -1,405 +0,0 @@ -/* - json_parse_state.js - 2016-05-02 - - Public Domain. - - NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. - - This file creates a json_parse function. - - json_parse(text, reviver) - This method parses a JSON text to produce an object or array. - It can throw a SyntaxError exception. - - The optional reviver parameter is a function that can filter and - transform the results. It receives each of the keys and values, - and its return value is used instead of the original value. - If it returns what it received, then the structure is not modified. - If it returns undefined then the member is deleted. - - Example: - - // Parse the text. Values that look like ISO date strings will - // be converted to Date objects. - - myData = json_parse(text, function (key, value) { - var a; - if (typeof value === "string") { - a = -/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); - if (a) { - return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], - +a[5], +a[6])); - } - } - return value; - }); - - This is a reference implementation. You are free to copy, modify, or - redistribute. - - This code should be minified before deployment. - See http://javascript.crockford.com/jsmin.html - - USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO - NOT CONTROL. -*/ - -/*jslint for */ - -/*property - acomma, avalue, b, call, colon, container, exec, f, false, firstavalue, - firstokey, fromCharCode, go, hasOwnProperty, key, length, n, null, ocomma, - okey, ovalue, pop, prototype, push, r, replace, slice, state, t, test, - true -*/ - -var json_parse = (function () { - "use strict"; - -// This function creates a JSON parse function that uses a state machine rather -// than the dangerous eval function to parse a JSON text. - - var state; // The state of the parser, one of - // 'go' The starting state - // 'ok' The final, accepting state - // 'firstokey' Ready for the first key of the object or - // the closing of an empty object - // 'okey' Ready for the next key of the object - // 'colon' Ready for the colon - // 'ovalue' Ready for the value half of a key/value pair - // 'ocomma' Ready for a comma or closing } - // 'firstavalue' Ready for the first value of an array or - // an empty array - // 'avalue' Ready for the next value of an array - // 'acomma' Ready for a comma or closing ] - var stack; // The stack, for controlling nesting. - var container; // The current container object or array - var key; // The current key - var value; // The current value - var escapes = { // Escapement translation table - "\\": "\\", - "\"": "\"", - "/": "/", - "t": "\t", - "n": "\n", - "r": "\r", - "f": "\f", - "b": "\b" - }; - var string = { // The actions for string tokens - go: function () { - state = "ok"; - }, - firstokey: function () { - key = value; - state = "colon"; - }, - okey: function () { - key = value; - state = "colon"; - }, - ovalue: function () { - state = "ocomma"; - }, - firstavalue: function () { - state = "acomma"; - }, - avalue: function () { - state = "acomma"; - } - }; - var number = { // The actions for number tokens - go: function () { - state = "ok"; - }, - ovalue: function () { - state = "ocomma"; - }, - firstavalue: function () { - state = "acomma"; - }, - avalue: function () { - state = "acomma"; - } - }; - var action = { - -// The action table describes the behavior of the machine. It contains an -// object for each token. Each object contains a method that is called when -// a token is matched in a state. An object will lack a method for illegal -// states. - - "{": { - go: function () { - stack.push({state: "ok"}); - container = {}; - state = "firstokey"; - }, - ovalue: function () { - stack.push({container: container, state: "ocomma", key: key}); - container = {}; - state = "firstokey"; - }, - firstavalue: function () { - stack.push({container: container, state: "acomma"}); - container = {}; - state = "firstokey"; - }, - avalue: function () { - stack.push({container: container, state: "acomma"}); - container = {}; - state = "firstokey"; - } - }, - "}": { - firstokey: function () { - var pop = stack.pop(); - value = container; - container = pop.container; - key = pop.key; - state = pop.state; - }, - ocomma: function () { - var pop = stack.pop(); - container[key] = value; - value = container; - container = pop.container; - key = pop.key; - state = pop.state; - } - }, - "[": { - go: function () { - stack.push({state: "ok"}); - container = []; - state = "firstavalue"; - }, - ovalue: function () { - stack.push({container: container, state: "ocomma", key: key}); - container = []; - state = "firstavalue"; - }, - firstavalue: function () { - stack.push({container: container, state: "acomma"}); - container = []; - state = "firstavalue"; - }, - avalue: function () { - stack.push({container: container, state: "acomma"}); - container = []; - state = "firstavalue"; - } - }, - "]": { - firstavalue: function () { - var pop = stack.pop(); - value = container; - container = pop.container; - key = pop.key; - state = pop.state; - }, - acomma: function () { - var pop = stack.pop(); - container.push(value); - value = container; - container = pop.container; - key = pop.key; - state = pop.state; - } - }, - ":": { - colon: function () { - if (Object.hasOwnProperty.call(container, key)) { - throw new SyntaxError("Duplicate key '" + key + "\""); - } - state = "ovalue"; - } - }, - ",": { - ocomma: function () { - container[key] = value; - state = "okey"; - }, - acomma: function () { - container.push(value); - state = "avalue"; - } - }, - "true": { - go: function () { - value = true; - state = "ok"; - }, - ovalue: function () { - value = true; - state = "ocomma"; - }, - firstavalue: function () { - value = true; - state = "acomma"; - }, - avalue: function () { - value = true; - state = "acomma"; - } - }, - "false": { - go: function () { - value = false; - state = "ok"; - }, - ovalue: function () { - value = false; - state = "ocomma"; - }, - firstavalue: function () { - value = false; - state = "acomma"; - }, - avalue: function () { - value = false; - state = "acomma"; - } - }, - "null": { - go: function () { - value = null; - state = "ok"; - }, - ovalue: function () { - value = null; - state = "ocomma"; - }, - firstavalue: function () { - value = null; - state = "acomma"; - }, - avalue: function () { - value = null; - state = "acomma"; - } - } - }; - - function debackslashify(text) { - -// Remove and replace any backslash escapement. - - return text.replace(/\\(?:u(.{4})|([^u]))/g, function (ignore, b, c) { - return b - ? String.fromCharCode(parseInt(b, 16)) - : escapes[c]; - }); - } - - return function (source, reviver) { - -// A regular expression is used to extract tokens from the JSON text. -// The extraction process is cautious. - - var result; - var tx = /^[\u0020\t\n\r]*(?:([,:\[\]{}]|true|false|null)|(-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)|"((?:[^\r\n\t\\\"]|\\(?:["\\\/trnfb]|u[0-9a-fA-F]{4}))*)")/; - -// Set the starting state. - - state = "go"; - -// The stack records the container, key, and state for each object or array -// that contains another object or array while processing nested structures. - - stack = []; - -// If any error occurs, we will catch it and ultimately throw a syntax error. - - try { - -// For each token... - - while (true) { - result = tx.exec(source); - if (!result) { - break; - } - -// result is the result array from matching the tokenizing regular expression. -// result[0] contains everything that matched, including any initial whitespace. -// result[1] contains any punctuation that was matched, or true, false, or null. -// result[2] contains a matched number, still in string form. -// result[3] contains a matched string, without quotes but with escapement. - - if (result[1]) { - -// Token: Execute the action for this state and token. - - action[result[1]][state](); - - } else if (result[2]) { - -// Number token: Convert the number string into a number value and execute -// the action for this state and number. - - value = +result[2]; - number[state](); - } else { - -// String token: Replace the escapement sequences and execute the action for -// this state and string. - - value = debackslashify(result[3]); - string[state](); - } - -// Remove the token from the string. The loop will continue as long as there -// are tokens. This is a slow process, but it allows the use of ^ matching, -// which assures that no illegal tokens slip through. - - source = source.slice(result[0].length); - } - -// If we find a state/token combination that is illegal, then the action will -// cause an error. We handle the error by simply changing the state. - - } catch (e) { - state = e; - } - -// The parsing is finished. If we are not in the final "ok" state, or if the -// remaining source contains anything except whitespace, then we did not have -//a well-formed JSON text. - - if (state !== "ok" || (/[^\u0020\t\n\r]/.test(source))) { - throw (state instanceof SyntaxError) - ? state - : new SyntaxError("JSON"); - } - -// 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 current -// value in an empty key. If there is not a reviver function, we simply return -// that value. - - 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 (v !== undefined) { - val[k] = v; - } else { - delete val[k]; - } - } - } - } - return reviver.call(holder, key, val); - }({"": value}, "")) - : value; - }; -}()); From aef828bfcd7d5efaa41270f831f8d27d5eef3845 Mon Sep 17 00:00:00 2001 From: Douglas Crockford Date: Tue, 28 May 2019 10:45:18 -0700 Subject: [PATCH 23/28] end of life --- README | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/README b/README index 6ca2c8c..ecb0b14 100644 --- a/README +++ b/README @@ -4,7 +4,7 @@ JSON in JavaScript Douglas Crockford douglas@crockford.com -2015-05-03 +2019-05-28 JSON is a light-weight, language independent, data interchange format. @@ -25,16 +25,10 @@ method and a parse method. The parse method uses the eval method to do the parsing, guarding it with several regular expressions to defend against accidental code execution hazards. On current browsers, this file does nothing, preferring the built-in JSON object. There is no reason to use this file unless -fate compels you to support IE8, which is something that no one should ever +fate compels you to support IE8, which is something that no one should ever have to do again. -json_parse.js: This file contains an alternative JSON parse function that -uses recursive descent instead of eval. - -json_parse_state.js: This files contains an alternative JSON parse function that -uses a state machine instead of eval. - cycle.js: This file contains two functions, JSON.decycle and JSON.retrocycle, which make it possible to encode cyclical structures and dags in JSON, and to -then recover them. This is a capability that is not provided by ES5. JSONPath +then recover them. This is a capability that is not provided by ES5. JSONPath is used to represent the links. [http://GOESSNER.net/articles/JsonPath/] From 594c8fa5f8e3fb38b0977f1ff8a87e9d709e7db1 Mon Sep 17 00:00:00 2001 From: Douglas Crockford Date: Mon, 26 Aug 2019 15:15:55 -0700 Subject: [PATCH 24/28] reamde --- README | 8 ++------ test.html | 36 ------------------------------------ 2 files changed, 2 insertions(+), 42 deletions(-) delete mode 100644 test.html diff --git a/README b/README index ecb0b14..55c2e67 100644 --- a/README +++ b/README @@ -4,19 +4,15 @@ JSON in JavaScript Douglas Crockford douglas@crockford.com -2019-05-28 +2019-08-25 JSON is a light-weight, language independent, data interchange format. See http://www.JSON.org/ -The files in this collection implement JSON encoders/decoders in JavaScript. - JSON became a built-in feature of JavaScript when the ECMAScript Programming Language Standard - Fifth Edition was adopted by the ECMA General Assembly -in December 2009. Most of the files in this collection are for applications -that are expected to run in obsolete web browsers. For most purposes, json2.js -is the best choice. +in December 2009. json2.js: This file creates a JSON property in the global object, if there diff --git a/test.html b/test.html deleted file mode 100644 index 26ca5a0..0000000 --- a/test.html +++ /dev/null @@ -1,36 +0,0 @@ - -

-
From 8e8b0407e475e35942f7e9461dab81929fcc7321 Mon Sep 17 00:00:00 2001 From: Douglas Crockford <262641+douglascrockford@users.noreply.github.com> Date: Mon, 31 May 2021 05:42:35 -0700 Subject: [PATCH 25/28] See https://www.crockford.com/jsmin.html Updated an html link in a comment. --- cycle.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cycle.js b/cycle.js index 5cc4547..c064644 100644 --- a/cycle.js +++ b/cycle.js @@ -1,13 +1,13 @@ /* cycle.js - 2018-05-15 + 2021-05-31 Public Domain. NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. This code should be minified before deployment. - See http://javascript.crockford.com/jsmin.html + See https://www.crockford.com/jsmin.html USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO NOT CONTROL. From b56ae5572062e366aa7ec57e8b0a80d9db109ffe Mon Sep 17 00:00:00 2001 From: Ildar Shaimordanov Date: Sun, 30 Oct 2022 10:21:38 +0300 Subject: [PATCH 26/28] Detect cyclic objects - detect cyclic objects - throw TypeError --- json2.js | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/json2.js b/json2.js index f6fada6..9bc78eb 100644 --- a/json2.js +++ b/json2.js @@ -1,5 +1,5 @@ // json2.js -// 2017-06-12 +// 2022-10-30 // Public Domain. // NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. @@ -231,6 +231,28 @@ if (typeof JSON !== "object") { } +// This variable is initialized with an empty array every time +// JSON.stringify() is invoked and checked by the str() function. It's +// used to keep references to object structures and capture cyclic +// objects. Every new object is checked for its existence in this +// array. If it's found it means the JSON object is cyclic and we have +// to stop execution and throw a TypeError accordingly the ECMA262 +// (see NOTE 1 by the link https://tc39.es/ecma262/#sec-json.stringify). + + var seen; + +// Emulate [].includes(). It's actual for old-fashioned JScript. + + function includes(array, value) { + for (var i = 0; i < array.length; i++) { + if (value === array[i]) { + return true; + } + } + return false; + } + + function str(key, holder) { // Produce a string from holder[key]. @@ -295,6 +317,16 @@ if (typeof JSON !== "object") { return "null"; } +// Check the value is not circular object. Otherwise throw TypeError. + + if (includes(seen, value)) { + throw new TypeError('Converting circular structure to JSON'); + } + +// Keep the value for the further check on circular references. + + seen.push(value); + // Make an array to hold the partial results of stringifying this object value. gap += indent; @@ -428,6 +460,10 @@ if (typeof JSON !== "object") { throw new Error("JSON.stringify"); } +// Initialize the reference keeper. + + seen = []; + // Make a fake root object containing our value under the key of "". // Return the result of stringifying the value. From 133e0f21069817788696a27895de118ad87abcd6 Mon Sep 17 00:00:00 2001 From: Ildar Shaimordanov Date: Sun, 30 Oct 2022 11:07:55 +0300 Subject: [PATCH 27/28] cosmetic change: follow the author code style --- json2.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/json2.js b/json2.js index 9bc78eb..adac444 100644 --- a/json2.js +++ b/json2.js @@ -244,7 +244,8 @@ if (typeof JSON !== "object") { // Emulate [].includes(). It's actual for old-fashioned JScript. function includes(array, value) { - for (var i = 0; i < array.length; i++) { + var i; + for (i = 0; i < array.length; i += 1) { if (value === array[i]) { return true; } @@ -320,7 +321,7 @@ if (typeof JSON !== "object") { // Check the value is not circular object. Otherwise throw TypeError. if (includes(seen, value)) { - throw new TypeError('Converting circular structure to JSON'); + throw new TypeError("Converting circular structure to JSON"); } // Keep the value for the further check on circular references. From 7e83f38a2312429fd4933169c1f6a27fd65e889c Mon Sep 17 00:00:00 2001 From: Douglas Crockford <262641+douglascrockford@users.noreply.github.com> Date: Wed, 10 May 2023 16:28:13 -0700 Subject: [PATCH 28/28] Revert json2.js Removed contributed code that did cycle detection badly. --- json2.js | 39 +-------------------------------------- 1 file changed, 1 insertion(+), 38 deletions(-) diff --git a/json2.js b/json2.js index adac444..b43526d 100644 --- a/json2.js +++ b/json2.js @@ -1,5 +1,5 @@ // json2.js -// 2022-10-30 +// 2023-05-10 // Public Domain. // NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. @@ -231,29 +231,6 @@ if (typeof JSON !== "object") { } -// This variable is initialized with an empty array every time -// JSON.stringify() is invoked and checked by the str() function. It's -// used to keep references to object structures and capture cyclic -// objects. Every new object is checked for its existence in this -// array. If it's found it means the JSON object is cyclic and we have -// to stop execution and throw a TypeError accordingly the ECMA262 -// (see NOTE 1 by the link https://tc39.es/ecma262/#sec-json.stringify). - - var seen; - -// Emulate [].includes(). It's actual for old-fashioned JScript. - - function includes(array, value) { - var i; - for (i = 0; i < array.length; i += 1) { - if (value === array[i]) { - return true; - } - } - return false; - } - - function str(key, holder) { // Produce a string from holder[key]. @@ -318,16 +295,6 @@ if (typeof JSON !== "object") { return "null"; } -// Check the value is not circular object. Otherwise throw TypeError. - - if (includes(seen, value)) { - throw new TypeError("Converting circular structure to JSON"); - } - -// Keep the value for the further check on circular references. - - seen.push(value); - // Make an array to hold the partial results of stringifying this object value. gap += indent; @@ -461,10 +428,6 @@ if (typeof JSON !== "object") { throw new Error("JSON.stringify"); } -// Initialize the reference keeper. - - seen = []; - // Make a fake root object containing our value under the key of "". // Return the result of stringifying the value.