json-stable-stringify
Advanced tools
Comparing version 1.1.1 to 1.2.0
@@ -8,2 +8,25 @@ # Changelog | ||
## [v1.2.0](https://github.com/ljharb/json-stable-stringify/compare/v1.1.1...v1.2.0) - 2024-12-17 | ||
### Fixed | ||
- [readme] remove dead badges [`#14`](https://github.com/ljharb/json-stable-stringify/issues/14) | ||
### Commits | ||
- [New] add types [`5dbd6c8`](https://github.com/ljharb/json-stable-stringify/commit/5dbd6c802fe013082e597ecf6a8c3428e60d906b) | ||
- [eslint] clean up formatting [`21e95e5`](https://github.com/ljharb/json-stable-stringify/commit/21e95e57ea55c6b7e8c63835391b1791a8ed9323) | ||
- [meta] sort package.json [`a9f44d5`](https://github.com/ljharb/json-stable-stringify/commit/a9f44d5e532e93e7cc48e384472c0a9da189bab9) | ||
- [actions] split out node 10-20, and 20+ [`74551e4`](https://github.com/ljharb/json-stable-stringify/commit/74551e4cc76ae90880b0471365de47d1c3dd1379) | ||
- [Tests] add test coverage for options provided directly on a cmp function [`0a50205`](https://github.com/ljharb/json-stable-stringify/commit/0a502052b9191f53f072599aac61aa829ac9e0ae) | ||
- [Robustness] cache more builtins [`d390c99`](https://github.com/ljharb/json-stable-stringify/commit/d390c99889ec80a20b77a9f73d8d8134f0fc11b8) | ||
- [Dev Deps] update `@ljharb/eslint-config`, `auto-changelog`, `tape` [`03686a0`](https://github.com/ljharb/json-stable-stringify/commit/03686a0af26444bac661e8ca0e70e3d13a4938d0) | ||
- [Tests] key ordering is reversed in node 11+ [`7034a17`](https://github.com/ljharb/json-stable-stringify/commit/7034a176d0dde8df1f899bf0f1c44e73f5792947) | ||
- [Dev Deps] update `npmignore`, `tape` [`ba8d519`](https://github.com/ljharb/json-stable-stringify/commit/ba8d519505f59725c9f4b0451332f6d64801a910) | ||
- [Refactor] use `call-bound` directly [`850b24c`](https://github.com/ljharb/json-stable-stringify/commit/850b24c5b3dc69b59804637c92c10f7bb3277ab8) | ||
- [Tests] replace `aud` with `npm audit` [`22fb720`](https://github.com/ljharb/json-stable-stringify/commit/22fb72061005f9b124a0dc84f8b87c3a977c00bd) | ||
- [Deps] update `call-bind` [`adc30b0`](https://github.com/ljharb/json-stable-stringify/commit/adc30b0746b58d469492e7586b1d32469dce4783) | ||
- [Deps] update `call-bind` [`a280582`](https://github.com/ljharb/json-stable-stringify/commit/a280582e6b8bb6e04642010931b60f9fda2fa0df) | ||
- [Dev Deps] add missing peer dep [`3bb517c`](https://github.com/ljharb/json-stable-stringify/commit/3bb517cc179cd90e841581046791d24cc2bee66a) | ||
## [v1.1.1](https://github.com/ljharb/json-stable-stringify/compare/v1.1.0...v1.1.1) - 2024-01-16 | ||
@@ -10,0 +33,0 @@ |
@@ -7,3 +7,4 @@ 'use strict'; | ||
var s = stringify(obj, function (a, b) { | ||
var s = stringify(obj, /** @type {import('..').Comparator} */ function (a, b) { | ||
// @ts-expect-error implicit coercion here is fine | ||
return a.value < b.value ? 1 : -1; | ||
@@ -10,0 +11,0 @@ }); |
112
index.js
'use strict'; | ||
/** @type {typeof JSON.stringify} */ | ||
var jsonStringify = (typeof JSON !== 'undefined' ? JSON : require('jsonify')).stringify; | ||
@@ -8,7 +9,10 @@ | ||
var callBind = require('call-bind'); | ||
var callBound = require('call-bind/callBound'); | ||
var callBound = require('call-bound'); | ||
var $join = callBound('Array.prototype.join'); | ||
var $push = callBound('Array.prototype.push'); | ||
var $indexOf = callBound('Array.prototype.indexOf'); | ||
var $splice = callBound('Array.prototype.splice'); | ||
var $sort = callBound('Array.prototype.sort'); | ||
/** @type {(n: number, char: string) => string} */ | ||
var strRepeat = function repeat(n, char) { | ||
@@ -22,5 +26,8 @@ var str = ''; | ||
var defaultReplacer = function (parent, key, value) { return value; }; | ||
/** @type {(parent: import('.').Node, key: import('.').Key, value: unknown) => unknown} */ | ||
var defaultReplacer = function (_parent, _key, value) { return value; }; | ||
/** @type {import('.')} */ | ||
module.exports = function stableStringify(obj) { | ||
/** @type {Parameters<import('.')>[1]} */ | ||
var opts = arguments.length > 1 ? arguments[1] : void undefined; | ||
@@ -30,12 +37,18 @@ var space = (opts && opts.space) || ''; | ||
var cycles = !!opts && typeof opts.cycles === 'boolean' && opts.cycles; | ||
/** @type {undefined | typeof defaultReplacer} */ | ||
var replacer = opts && opts.replacer ? callBind(opts.replacer) : defaultReplacer; | ||
var cmpOpt = typeof opts === 'function' ? opts : opts && opts.cmp; | ||
/** @type {undefined | (<T extends import('.').NonArrayNode>(node: T) => (a: Exclude<keyof T, symbol | number>, b: Exclude<keyof T, symbol | number>) => number)} */ | ||
var cmp = cmpOpt && function (node) { | ||
var get = cmpOpt.length > 2 && function get(k) { return node[k]; }; | ||
// eslint-disable-next-line no-extra-parens | ||
var get = /** @type {NonNullable<typeof cmpOpt>} */ (cmpOpt).length > 2 | ||
&& /** @type {import('.').Getter['get']} */ function get(k) { return node[k]; }; | ||
return function (a, b) { | ||
return cmpOpt( | ||
// eslint-disable-next-line no-extra-parens | ||
return /** @type {NonNullable<typeof cmpOpt>} */ (cmpOpt)( | ||
{ key: a, value: node[a] }, | ||
{ key: b, value: node[b] }, | ||
get ? { __proto__: null, get: get } : void undefined | ||
// @ts-expect-error TS doesn't understand the optimization used here | ||
get ? /** @type {import('.').Getter} */ { __proto__: null, get: get } : void undefined | ||
); | ||
@@ -45,51 +58,60 @@ }; | ||
/** @type {import('.').Node[]} */ | ||
var seen = []; | ||
return (function stringify(parent, key, node, level) { | ||
var indent = space ? '\n' + strRepeat(level, space) : ''; | ||
var colonSeparator = space ? ': ' : ':'; | ||
return (/** @type {(parent: import('.').Node, key: string | number, node: unknown, level: number) => string | undefined} */ | ||
function stringify(parent, key, node, level) { | ||
var indent = space ? '\n' + strRepeat(level, space) : ''; | ||
var colonSeparator = space ? ': ' : ':'; | ||
if (node && node.toJSON && typeof node.toJSON === 'function') { | ||
node = node.toJSON(); | ||
} | ||
// eslint-disable-next-line no-extra-parens | ||
if (node && /** @type {{ toJSON?: unknown }} */ (node).toJSON && typeof /** @type {{ toJSON?: unknown }} */ (node).toJSON === 'function') { | ||
// eslint-disable-next-line no-extra-parens | ||
node = /** @type {{ toJSON: Function }} */ (node).toJSON(); | ||
} | ||
node = replacer(parent, key, node); | ||
node = replacer(parent, key, node); | ||
if (node === undefined) { | ||
return; | ||
} | ||
if (typeof node !== 'object' || node === null) { | ||
return jsonStringify(node); | ||
} | ||
if (isArray(node)) { | ||
var out = []; | ||
for (var i = 0; i < node.length; i++) { | ||
var item = stringify(node, i, node[i], level + 1) || jsonStringify(null); | ||
$push(out, indent + space + item); | ||
if (node === undefined) { | ||
return; | ||
} | ||
return '[' + $join(out, ',') + indent + ']'; | ||
} | ||
if (typeof node !== 'object' || node === null) { | ||
return jsonStringify(node); | ||
} | ||
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 + ']'; | ||
} | ||
if (seen.indexOf(node) !== -1) { | ||
if (cycles) { return jsonStringify('__cycle__'); } | ||
throw new TypeError('Converting circular structure to JSON'); | ||
} else { $push(seen, node); } | ||
if ($indexOf(seen, node) !== -1) { | ||
if (cycles) { return jsonStringify('__cycle__'); } | ||
throw new TypeError('Converting circular structure to JSON'); | ||
} else { | ||
seen[seen.length] = /** @type {import('.').NonArrayNode} */ (node); | ||
} | ||
var keys = objectKeys(node).sort(cmp && cmp(node)); | ||
var out = []; | ||
for (var i = 0; i < keys.length; i++) { | ||
var key = keys[i]; | ||
var value = stringify(node, key, node[key], level + 1); | ||
/** @type {import('.').Key[]} */ | ||
// eslint-disable-next-line no-extra-parens | ||
var keys = $sort(objectKeys(node), cmp && cmp(/** @type {import('.').NonArrayNode} */ (node))); | ||
var out = []; | ||
for (var i = 0; i < keys.length; i++) { | ||
var key = keys[i]; | ||
// eslint-disable-next-line no-extra-parens | ||
var value = stringify(/** @type {import('.').Node} */ (node), key, /** @type {import('.').NonArrayNode} */ (node)[key], level + 1); | ||
if (!value) { continue; } | ||
if (!value) { continue; } | ||
var keyValue = jsonStringify(key) | ||
+ colonSeparator | ||
+ value; | ||
var keyValue = jsonStringify(key) | ||
+ colonSeparator | ||
+ value; | ||
$push(out, indent + space + keyValue); | ||
} | ||
seen.splice(seen.indexOf(node), 1); | ||
return '{' + $join(out, ',') + indent + '}'; | ||
}({ '': obj }, '', obj, 0)); | ||
out[out.length] = indent + space + keyValue; | ||
} | ||
$splice(seen, $indexOf(seen, node), 1); | ||
return '{' + $join(out, ',') + indent + '}'; | ||
}({ '': obj }, '', obj, 0) | ||
); | ||
}; |
{ | ||
"name": "json-stable-stringify", | ||
"version": "1.1.1", | ||
"version": "1.2.0", | ||
"description": "deterministic JSON.stringify() with custom sorting to get deterministic hashes from stringified results", | ||
"main": "index.js", | ||
"dependencies": { | ||
"call-bind": "^1.0.5", | ||
"isarray": "^2.0.5", | ||
"jsonify": "^0.0.1", | ||
"object-keys": "^1.1.1" | ||
}, | ||
"devDependencies": { | ||
"@ljharb/eslint-config": "^21.1.0", | ||
"aud": "^2.0.4", | ||
"auto-changelog": "^2.4.0", | ||
"eslint": "=8.8.0", | ||
"in-publish": "^2.0.1", | ||
"npmignore": "^0.3.0", | ||
"safe-publish-latest": "^2.0.0", | ||
"tape": "^5.7.3" | ||
}, | ||
"scripts": { | ||
@@ -27,21 +11,10 @@ "prepack": "npmignore --auto --commentLines=autogenerated", | ||
"lint": "eslint --ext=js,mjs .", | ||
"postlint": "tsc && attw -P", | ||
"pretest": "npm run lint", | ||
"tests-only": "tape 'test/**/*.js'", | ||
"test": "npm run tests-only", | ||
"posttest": "aud --production", | ||
"posttest": "npx npm@'>= 10.2' audit --production", | ||
"version": "auto-changelog && git add CHANGELOG.md", | ||
"postversion": "auto-changelog && git add CHANGELOG.md && git commit --no-edit --amend && git tag -f \"v$(node -e \"console.log(require('./package.json').version)\")\"" | ||
}, | ||
"testling": { | ||
"files": "test/*.js", | ||
"browsers": [ | ||
"ie/8..latest", | ||
"ff/5", | ||
"ff/latest", | ||
"chrome/15", | ||
"chrome/latest", | ||
"safari/latest", | ||
"opera/latest" | ||
] | ||
}, | ||
"repository": { | ||
@@ -51,3 +24,2 @@ "type": "git", | ||
}, | ||
"homepage": "https://github.com/ljharb/json-stable-stringify", | ||
"keywords": [ | ||
@@ -66,9 +38,40 @@ "json", | ||
}, | ||
"license": "MIT", | ||
"bugs": { | ||
"url": "https://github.com/ljharb/json-stable-stringify/issues" | ||
}, | ||
"homepage": "https://github.com/ljharb/json-stable-stringify", | ||
"funding": { | ||
"url": "https://github.com/sponsors/ljharb" | ||
}, | ||
"license": "MIT", | ||
"dependencies": { | ||
"call-bind": "^1.0.8", | ||
"call-bound": "^1.0.3", | ||
"isarray": "^2.0.5", | ||
"jsonify": "^0.0.1", | ||
"object-keys": "^1.1.1" | ||
}, | ||
"devDependencies": { | ||
"@arethetypeswrong/cli": "^0.17.1", | ||
"@ljharb/eslint-config": "^21.1.1", | ||
"@ljharb/tsconfig": "^0.2.2", | ||
"@types/call-bind": "^1.0.5", | ||
"@types/isarray": "^2.0.3", | ||
"@types/object-keys": "^1.0.3", | ||
"@types/tape": "^5.7.0", | ||
"auto-changelog": "^2.5.0", | ||
"encoding": "^0.1.13", | ||
"eslint": "=8.8.0", | ||
"in-publish": "^2.0.1", | ||
"npmignore": "^0.3.1", | ||
"safe-publish-latest": "^2.0.0", | ||
"tape": "^5.9.0", | ||
"typescript": "next" | ||
}, | ||
"engines": { | ||
"node": ">= 0.4" | ||
}, | ||
"testling": { | ||
"files": "test/*.js" | ||
}, | ||
"auto-changelog": { | ||
@@ -84,5 +87,6 @@ "output": "CHANGELOG.md", | ||
"ignore": [ | ||
".github/workflows" | ||
".github/workflows", | ||
"types" | ||
] | ||
} | ||
} |
@@ -14,6 +14,2 @@ # json-stable-stringify <sup>[![Version Badge][npm-version-svg]][package-url]</sup> | ||
[![browser support](https://ci.testling.com/ljharb/json-stable-stringify.png)](https://ci.testling.com/ljharb/json-stable-stringify) | ||
[![build status](https://secure.travis-ci.org/ljharb/json-stable-stringify.png)](http://travis-ci.org/ljharb/json-stable-stringify) | ||
# example | ||
@@ -20,0 +16,0 @@ |
@@ -18,4 +18,5 @@ 'use strict'; | ||
stringify({ a: 1, b: 2 }, function (a, b) { // eslint-disable-line no-unused-vars | ||
stringify({ a: 1, b: 2 }, /** @type {import('..').Comparator} */ function (_a, _b) { // eslint-disable-line no-unused-vars | ||
t.equal(arguments[2], undefined, 'comparator options not passed when not explicitly requested'); | ||
return NaN; | ||
}); | ||
@@ -26,3 +27,5 @@ | ||
var get = options.get; | ||
// @ts-expect-error implicit coercion here is fine | ||
var v1 = (get('!' + a.key) || 0) + a.value; | ||
// @ts-expect-error implicit coercion here is fine | ||
var v2 = (get('!' + b.key) || 0) + b.value; | ||
@@ -29,0 +32,0 @@ return v1 - v2; |
@@ -14,3 +14,3 @@ 'use strict'; | ||
t.plan(1); | ||
var one = { a: 1 }; | ||
var one = { a: 1, two: {} }; | ||
var two = { a: 2, one: one }; | ||
@@ -21,3 +21,7 @@ one.two = two; | ||
} catch (ex) { | ||
t.equal(ex.toString(), 'TypeError: Converting circular structure to JSON'); | ||
if (ex == null) { // eslint-disable-line eqeqeq | ||
t.fail('nullish exception'); | ||
} else { | ||
t.equal(ex.toString(), 'TypeError: Converting circular structure to JSON'); | ||
} | ||
} | ||
@@ -28,3 +32,3 @@ }); | ||
t.plan(1); | ||
var one = { a: 1 }; | ||
var one = { a: 1, two: {} }; | ||
var two = { a: 2, one: one }; | ||
@@ -31,0 +35,0 @@ one.two = two; |
@@ -19,3 +19,4 @@ 'use strict'; | ||
var obj = { a: 1, b: 2, c: false }; | ||
var replacer = function (key, value) { | ||
/** @type {import('..').StableStringifyOptions['replacer']} */ | ||
var replacer = function (_key, value) { | ||
if (value === 1) { return 'one'; } | ||
@@ -33,2 +34,3 @@ if (value === 2) { return 'two'; } | ||
var obj = { a: 1, b: 2, c: false }; | ||
/** @type {import('..').StableStringifyOptions['replacer']} */ | ||
var replacer = function (key, value) { | ||
@@ -47,3 +49,4 @@ if (key === 'b') { return { d: 1 }; } | ||
var obj = { a: 1, b: 2, c: false }; | ||
var replacer = function (key, value) { | ||
/** @type {import('..').StableStringifyOptions['replacer']} */ | ||
var replacer = function (_key, value) { | ||
if (value === false) { return; } | ||
@@ -60,2 +63,3 @@ return value; | ||
var obj = { a: 1, b: 2, c: false }; | ||
/** @type {import('..').StableStringifyOptions['replacer']} */ | ||
var replacer = function (key, value) { | ||
@@ -73,3 +77,4 @@ if (key === 'b') { return ['one', 'two']; } | ||
var obj = { a: 1, b: 2, c: [1, 2] }; | ||
var replacer = function (key, value) { | ||
/** @type {import('..').StableStringifyOptions['replacer']} */ | ||
var replacer = function (_key, value) { | ||
if (value === 1) { return 'one'; } | ||
@@ -76,0 +81,0 @@ if (value === 2) { return 'two'; } |
@@ -6,2 +6,5 @@ 'use strict'; | ||
// @ts-expect-error node ensures this will never fail | ||
var isNode10OrLess = parseInt(process.version.match(/^v(\d+)\./)[1], 10) <= 10; | ||
test('space parameter', function (t) { | ||
@@ -74,1 +77,28 @@ t.plan(1); | ||
}); | ||
test('space parameter, on a cmp function', function (t) { | ||
t.plan(3); | ||
var obj = { one: 1, two: 2 }; | ||
/** @type {import('..').Comparator & import('../').StableStringifyOptions} */ | ||
var cmp = function (a, b) { | ||
return (a < b ? 1 : -1) * (isNode10OrLess ? -1 : 1); | ||
}; | ||
t.equal( | ||
stringify(obj, { space: '\t' }), | ||
'{\n\t"one": 1,\n\t"two": 2\n}', | ||
'no cmp option (control)' | ||
); | ||
t.equal( | ||
stringify(obj, { cmp: cmp, space: '\t' }), | ||
'{\n\t"two": 2,\n\t"one": 1\n}', | ||
'cmp option in the object' | ||
); | ||
cmp.space = '\t'; | ||
t.equal( | ||
stringify(obj, cmp), | ||
'{\n\t"two": 2,\n\t"one": 1\n}', | ||
'cmp passed directly, with space option on it' | ||
); | ||
}); |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No bug tracker
MaintenancePackage does not have a linked bug tracker in package.json.
Found 1 instance in 1 package
34048
19
418
0
5
15
158
+ Addedcall-bound@^1.0.3
+ Addedcall-bound@1.0.3(transitive)
+ Addedes-object-atoms@1.0.1(transitive)
- Removedes-object-atoms@1.0.0(transitive)
Updatedcall-bind@^1.0.8