From 59c4a2c111962e73a4aaa8c9068f3091be4ee538 Mon Sep 17 00:00:00 2001 From: Devon Wolfkiel Date: Fri, 11 Apr 2025 10:33:12 -0700 Subject: [PATCH] [New] add `collapseEmpty` option New option to remove newlines in empty arrays and objects Fixes #15 --- index.d.ts | 1 + index.js | 17 ++++++++++++++--- test/space.js | 25 +++++++++++++++++++++++++ 3 files changed, 40 insertions(+), 3 deletions(-) diff --git a/index.d.ts b/index.d.ts index 4f677f8..d42fb1d 100644 --- a/index.d.ts +++ b/index.d.ts @@ -10,6 +10,7 @@ declare namespace stableStringify { type StableStringifyOptions = { cmp?: Comparator; + collapseEmpty?: boolean; cycles?: boolean; replacer?: (this: Node, key: Key, value: unknown) => unknown; space?: string | number; diff --git a/index.js b/index.js index abeaaeb..acf3fcf 100644 --- a/index.js +++ b/index.js @@ -34,6 +34,10 @@ module.exports = function stableStringify(obj) { var cycles = !!opts && typeof opts.cycles === 'boolean' && opts.cycles; /** @type {undefined | typeof defaultReplacer} */ var replacer = opts && opts.replacer ? callBind(opts.replacer) : defaultReplacer; + if (opts && typeof opts.collapseEmpty !== 'undefined' && typeof opts.collapseEmpty !== 'boolean') { + throw new TypeError('`collapseEmpty` must be a boolean, if provided'); + } + var collapseEmpty = !!opts && opts.collapseEmpty; var cmpOpt = typeof opts === 'function' ? opts : opts && opts.cmp; /** @type {undefined | ((node: T) => (a: Exclude, b: Exclude) => number)} */ @@ -66,20 +70,27 @@ module.exports = function stableStringify(obj) { } node = replacer(parent, key, node); - if (node === undefined) { return; } if (typeof node !== 'object' || node === null) { return jsonStringify(node); } + + /** @type {(out: string[], brackets: '[]' | '{}') => string} */ + var groupOutput = function (out, brackets) { + return collapseEmpty && out.length === 0 + ? brackets + : (brackets === '[]' ? '[' : '{') + $join(out, ',') + indent + (brackets === '[]' ? ']' : '}'); + }; + if (isArray(node)) { var out = []; for (var i = 0; i < node.length; i++) { var item = stringify(node, i, node[i], level + 1) || jsonStringify(null); out[out.length] = indent + space + item; } - return '[' + $join(out, ',') + indent + ']'; + return groupOutput(out, '[]'); } if ($indexOf(seen, node) !== -1) { @@ -107,7 +118,7 @@ module.exports = function stableStringify(obj) { out[out.length] = indent + space + keyValue; } $splice(seen, $indexOf(seen, node), 1); - return '{' + $join(out, ',') + indent + '}'; + return groupOutput(out, '{}'); }({ '': obj }, '', obj, 0) ); }; diff --git a/test/space.js b/test/space.js index de17128..913df08 100644 --- a/test/space.js +++ b/test/space.js @@ -74,6 +74,31 @@ test('space parameter (same as native)', function (t) { ); }); +test('space parameter base empty behavior: empty arrays and objects have added newline and space', function (t) { + t.plan(1); + var obj = { emptyArr: [], emptyObj: {} }; + t.equal( + stringify(obj, { space: ' ' }), + '{\n "emptyArr": [\n ],\n "emptyObj": {\n }\n}' + ); +}); + +test('space parameter, with collapseEmpty: true', function (t) { + t.plan(2); + var obj = { emptyArr: [], emptyObj: {} }; + + t['throws']( + // @ts-expect-error + function () { stringify(obj, { collapseEmpty: 'not a boolean' }); }, + TypeError + ); + + t.equal( + stringify(obj, { collapseEmpty: true, space: ' ' }), + '{\n "emptyArr": [],\n "emptyObj": {}\n}' + ); +}); + test('space parameter, on a cmp function', function (t) { t.plan(3); var obj = { one: 1, two: 2 };