Skip to content

Commit b95b0d8

Browse files
committed
util: add order option to .inspect()
The order option can be used to sort the inspected values in case they do not rely on their order as arrays. That way the output is stable no matter of the object property inspection order. PR-URL: nodejs#22788 Refs: nodejs#22763 Reviewed-By: John-David Dalton <john.david.dalton@gmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
1 parent 9d71e6a commit b95b0d8

File tree

3 files changed

+82
-3
lines changed

3 files changed

+82
-3
lines changed

doc/api/util.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,9 @@ stream.write('With ES6');
360360
<!-- YAML
361361
added: v0.3.0
362362
changes:
363+
- version: REPLACEME
364+
pr-url: https://github.com/nodejs/node/pull/22788
365+
description: The `sorted` option is supported now.
363366
- version: REPLACEME
364367
pr-url: https://github.com/nodejs/node/pull/22756
365368
description: The inspection output is now limited to about 128 MB. Data
@@ -426,6 +429,10 @@ changes:
426429
objects the same as arrays. Note that no text will be reduced below 16
427430
characters, no matter the `breakLength` size. For more information, see the
428431
example below. **Default:** `true`.
432+
* `sorted` {boolean|Function} If set to `true` or a function, all properties
433+
of an object and Set and Map entries will be sorted in the returned string.
434+
If set to `true` the [default sort][] is going to be used. If set to a
435+
function, it is used as a [compare function][].
429436
* Returns: {string} The representation of passed object
430437

431438
The `util.inspect()` method returns a string representation of `object` that is
@@ -535,6 +542,34 @@ console.log(inspect(weakSet, { showHidden: true }));
535542
// WeakSet { { a: 1 }, { b: 2 } }
536543
```
537544

545+
The `sorted` option makes sure the output is identical, no matter of the
546+
properties insertion order:
547+
548+
```js
549+
const { inspect } = require('util');
550+
const assert = require('assert');
551+
552+
const o1 = {
553+
b: [2, 3, 1],
554+
a: '`a` comes before `b`',
555+
c: new Set([2, 3, 1])
556+
};
557+
console.log(inspect(o1, { sorted: true }));
558+
// { a: '`a` comes before `b`', b: [ 2, 3, 1 ], c: Set { 1, 2, 3 } }
559+
console.log(inspect(o1, { sorted: (a, b) => a < b }));
560+
// { c: Set { 3, 2, 1 }, b: [ 2, 3, 1 ], a: '`a` comes before `b`' }
561+
562+
const o2 = {
563+
c: new Set([2, 1, 3]),
564+
a: '`a` comes before `b`',
565+
b: [2, 3, 1]
566+
};
567+
assert.strict.equal(
568+
inspect(o1, { sorted: true }),
569+
inspect(o2, { sorted: true })
570+
);
571+
```
572+
538573
Please note that `util.inspect()` is a synchronous method that is mainly
539574
intended as a debugging tool. Its maximum output length is limited to
540575
approximately 128 MB and input values that result in output bigger than that
@@ -2165,7 +2200,9 @@ Deprecated predecessor of `console.log`.
21652200
[WHATWG Encoding Standard]: https://encoding.spec.whatwg.org/
21662201
[Common System Errors]: errors.html#errors_common_system_errors
21672202
[async function]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
2203+
[compare function]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#Parameters
21682204
[constructor]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/constructor
2205+
[default sort]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
21692206
[global symbol registry]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/for
21702207
[list of deprecated APIS]: deprecations.html#deprecations_list_of_deprecated_apis
21712208
[semantically incompatible]: https://github.com/nodejs/node/issues/4179

lib/util.js

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,8 @@ const inspectDefaultOptions = Object.seal({
9999
showProxy: false,
100100
maxArrayLength: 100,
101101
breakLength: 60,
102-
compact: true
102+
compact: true,
103+
sorted: false
103104
});
104105

105106
const kObjectType = 0;
@@ -394,6 +395,8 @@ function debuglog(set) {
394395
function inspect(value, opts) {
395396
// Default options
396397
const ctx = {
398+
budget: {},
399+
indentationLvl: 0,
397400
seen: [],
398401
stylize: stylizeNoColor,
399402
showHidden: inspectDefaultOptions.showHidden,
@@ -405,9 +408,8 @@ function inspect(value, opts) {
405408
// `maxEntries`.
406409
maxArrayLength: inspectDefaultOptions.maxArrayLength,
407410
breakLength: inspectDefaultOptions.breakLength,
408-
indentationLvl: 0,
409411
compact: inspectDefaultOptions.compact,
410-
budget: {}
412+
sorted: inspectDefaultOptions.sorted
411413
};
412414
if (arguments.length > 1) {
413415
// Legacy...
@@ -894,6 +896,16 @@ function formatRaw(ctx, value, recurseTimes) {
894896
}
895897
ctx.seen.pop();
896898

899+
if (ctx.sorted) {
900+
const comparator = ctx.sorted === true ? undefined : ctx.sorted;
901+
if (extrasType === kObjectType) {
902+
output = output.sort(comparator);
903+
} else if (keys.length > 1) {
904+
const sorted = output.slice(output.length - keys.length).sort(comparator);
905+
output.splice(output.length - keys.length, keys.length, ...sorted);
906+
}
907+
}
908+
897909
const res = reduceToSingleString(ctx, output, base, braces);
898910
const budget = ctx.budget[ctx.indentationLvl] || 0;
899911
const newLength = budget + res.length;

test/parallel/test-util-inspect.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1676,3 +1676,33 @@ assert.strictEqual(inspect(new BigUint64Array([0n])), 'BigUint64Array [ 0n ]');
16761676
);
16771677
rejection.catch(() => {});
16781678
}
1679+
1680+
assert.strictEqual(
1681+
inspect([1, 3, 2], { sorted: true }),
1682+
inspect([1, 3, 2])
1683+
);
1684+
assert.strictEqual(
1685+
inspect({ c: 3, a: 1, b: 2 }, { sorted: true }),
1686+
'{ a: 1, b: 2, c: 3 }'
1687+
);
1688+
assert.strictEqual(
1689+
inspect(
1690+
{ a200: 4, a100: 1, a102: 3, a101: 2 },
1691+
{ sorted(a, b) { return a < b; } }
1692+
),
1693+
'{ a200: 4, a102: 3, a101: 2, a100: 1 }'
1694+
);
1695+
1696+
// Non-indices array properties are sorted as well.
1697+
{
1698+
const arr = [3, 2, 1];
1699+
arr.b = 2;
1700+
arr.c = 3;
1701+
arr.a = 1;
1702+
arr[Symbol('b')] = true;
1703+
arr[Symbol('a')] = false;
1704+
assert.strictEqual(
1705+
inspect(arr, { sorted: true }),
1706+
'[ 3, 2, 1, [Symbol(a)]: false, [Symbol(b)]: true, a: 1, b: 2, c: 3 ]'
1707+
);
1708+
}

0 commit comments

Comments
 (0)