From 0d8bfbae8a6db367d1975e744ef7e54ffa296571 Mon Sep 17 00:00:00 2001 From: Naomi Seyfer Date: Tue, 1 Oct 2013 11:57:53 -0700 Subject: [PATCH] fix tests, rename option, more style fixes --- packages/ejson/ejson.js | 14 +- packages/ejson/ejson_test.js | 16 ++- packages/ejson/package.js | 2 +- packages/ejson/stringify.js | 254 +++++++++++++---------------------- 4 files changed, 109 insertions(+), 177 deletions(-) diff --git a/packages/ejson/ejson.js b/packages/ejson/ejson.js index 3d7d3160d0..af601fbca9 100644 --- a/packages/ejson/ejson.js +++ b/packages/ejson/ejson.js @@ -207,15 +207,12 @@ EJSON.fromJSONValue = function (item) { }; EJSON.stringify = function (item, options) { - var keyOrderSensitive = !!(options && options.keyOrderSensitive); - var indent = options && options.indent || null; - if (indent === true) - indent = 2; var json = EJSON.toJSONValue(item); - if (keyOrderSensitive) - return JSON.stringify(json, null, indent); - else - return EJSON._canonicalStringify(json, null, indent); + if (options && (options.canonical || options.indent)) { + return EJSON._canonicalStringify(json, options); + } else { + return JSON.stringify(json); + } }; EJSON.parse = function (item) { @@ -315,6 +312,7 @@ EJSON.clone = function (v) { } return ret; } + // XXX: Use something better than underscore's isArray if (_.isArray(v) || _.isArguments(v)) { // For some reason, _.map doesn't work in this context on Opera (weird test // failures). diff --git a/packages/ejson/ejson_test.js b/packages/ejson/ejson_test.js index ed438caa99..c71684e82f 100644 --- a/packages/ejson/ejson_test.js +++ b/packages/ejson/ejson_test.js @@ -86,10 +86,10 @@ Tinytest.add("ejson - stringify", function (test) { test.equal(EJSON.stringify([1, 2, 3], {indent: true}), "[\n 1,\n 2,\n 3\n]" ); - test.equal(EJSON.stringify([1, 2, 3], {keyOrderSensitive: true}), + test.equal(EJSON.stringify([1, 2, 3], {canonical: false}), "[1,2,3]" ); - test.equal(EJSON.stringify([1, 2, 3], {indent: true, keyOrderSensitive: true}), + test.equal(EJSON.stringify([1, 2, 3], {indent: true, canonical: false}), "[\n 1,\n 2,\n 3\n]" ); @@ -102,14 +102,18 @@ Tinytest.add("ejson - stringify", function (test) { test.equal( EJSON.stringify( - {b: [2, {d: 4, c: 3}], a: 1} + {b: [2, {d: 4, c: 3}], a: 1}, + {canonical: true} ), "{\"a\":1,\"b\":[2,{\"c\":3,\"d\":4}]}" ); test.equal( EJSON.stringify( {b: [2, {d: 4, c: 3}], a: 1}, - {indent: true} + { + indent: true, + canonical: true + } ), "{\n" + " \"a\": 1,\n" + @@ -125,14 +129,14 @@ Tinytest.add("ejson - stringify", function (test) { test.equal( EJSON.stringify( {b: [2, {d: 4, c: 3}], a: 1}, - {keyOrderSensitive: true} + {canonical: false} ), "{\"b\":[2,{\"d\":4,\"c\":3}],\"a\":1}" ); test.equal( EJSON.stringify( {b: [2, {d: 4, c: 3}], a: 1}, - {indent: true, keyOrderSensitive: true} + {indent: true, canonical: false} ), "{\n" + " \"b\": [\n" + diff --git a/packages/ejson/package.js b/packages/ejson/package.js index 400f1b25e6..226c233baf 100644 --- a/packages/ejson/package.js +++ b/packages/ejson/package.js @@ -8,8 +8,8 @@ Package.on_use(function (api) { api.export('EJSON'); api.export('EJSONTest', {testOnly: true}); api.add_files('ejson.js', ['client', 'server']); - api.add_files('base64.js', ['client', 'server']); api.add_files('stringify.js', ['client', 'server']); + api.add_files('base64.js', ['client', 'server']); }); Package.on_test(function (api) { diff --git a/packages/ejson/stringify.js b/packages/ejson/stringify.js index b82880200e..1ce9ad6baf 100644 --- a/packages/ejson/stringify.js +++ b/packages/ejson/stringify.js @@ -7,182 +7,112 @@ // // NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. -EJSON = {}; // Global! - function quote(string) { return JSON.stringify(string); } -var rep, gap, indent; +var str = function (key, holder, singleIndent, outerIndent, canonical) { -function str(key, holder) { + // Produce a string from holder[key]. -// Produce a string from holder[key]. + var i; // The loop counter. + var k; // The member key. + var v; // The member value. + var length; + var innerIndent = outerIndent; + var partial; + var value = holder[key]; - var i, // The loop counter. - k, // The member key. - v, // The member value. - length, - mind = gap, - partial, - value = holder[key]; + // What happens next depends on the value's type. -// If the value has a toJSON method, call it to obtain a replacement value. + 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': + 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. + innerIndent = outerIndent + singleIndent; + partial = []; - if (value && typeof value === 'object' && - typeof value.toJSON === 'function') { - value = value.toJSON(key); + // Is the value an array? + if (_.isArray(value) || _.isArguments(value)) { + + // 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, singleIndent, innerIndent, canonical) || 'null'; + } + + // Join all of the elements together, separated with commas, and wrap them in + // brackets. + + if (partial.length === 0) { + v = '[]'; + } else if (innerIndent) { + v = '[\n' + innerIndent + partial.join(',\n' + innerIndent) + '\n' + outerIndent + ']'; + } else { + v = '[' + partial.join(',') + ']'; + } + return v; } -// 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) { - if (typeof rep[i] === 'string') { - k = rep[i]; - v = str(k, value); - if (v) { - partial.push(quote(k) + (gap ? ': ' : ':') + v); - } - } - } - } else { - -// Otherwise, iterate through all of the keys in the object. - - _.each(_.keys(value).sort(), function (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; + // Iterate through all of the keys in the object. + var keys = _.keys(value); + if (canonical) + keys = keys.sort(); + _.each(keys, function (k) { + v = str(k, value, singleIndent, innerIndent, canonical); + if (v) { + partial.push(quote(k) + (innerIndent ? ': ' : ':') + v); + } + }); + + + // Join all of the member texts together, separated with commas, + // and wrap them in braces. + + if (partial.length === 0) { + v = '{}'; + } else if (innerIndent) { + v = '{\n' + innerIndent + partial.join(',\n' + innerIndent) + '\n' + outerIndent + '}'; + } else { + v = '{' + partial.join(',') + '}'; } + return v; + } } // If the JSON object does not yet have a stringify method, give it one. -function stringify(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}); - } - -EJSON._canonicalStringify = stringify; +EJSON._canonicalStringify = function (value, options) { + // Make a fake root object containing our value under the key of ''. + // Return the result of stringifying the value. + options = _.extend({ + indent: "", + canonical: false + }, options); + if (options.indent === true) { + options.indent = " "; + } else if (typeof options.indent === 'number') { + var newIndent = ""; + for (var i = 0; i < options.indent; i++) { + newIndent += ' '; + } + options.indent = newIndent; + } + return str('', {'': value}, options.indent, "", options.canonical); +};