From 778d4f123ccd429ab0e9f593d33e8805675fb1cd Mon Sep 17 00:00:00 2001 From: Mark Larah Date: Wed, 12 Aug 2026 15:44:57 -0500 Subject: [PATCH 1/4] console: add console.json() method Adds `console.json(...values)` which serializes each argument using `JSON.stringify` with 2-space indentation, printing to stdout. Each value is printed on a separate line. Motivation: inspecting objects via `console.log` applies `util.inspect` formatting, which adds annotations like `[Object: null prototype]` that are noise when you just want the plain data. `JSON.stringify` already handles null-prototype objects cleanly, so this is a thin wrapper that makes the common pattern `console.log(JSON.stringify(x, null, 2))` first-class. Non-serializable top-level values (undefined, functions, symbols) are silently skipped, matching the behavior of JSON.stringify returning undefined for them. Circular references throw a TypeError. Refs: https://github.com/whatwg/console/issues/222 Co-Authored-By: Claude --- doc/api/console.md | 30 +++++++++++++ lib/internal/console/constructor.js | 11 ++++- test/parallel/test-console-json.js | 65 +++++++++++++++++++++++++++ test/parallel/test-console-methods.js | 1 + 4 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 test/parallel/test-console-json.js diff --git a/doc/api/console.md b/doc/api/console.md index a8369290dfbf..eab7d44edcad 100644 --- a/doc/api/console.md +++ b/doc/api/console.md @@ -328,6 +328,36 @@ added: v0.1.100 The `console.info()` function is an alias for [`console.log()`][]. +### console.json(...values) + +* `...values` {any} + +Serializes each argument as JSON using [`JSON.stringify()`][] with 2-space +indentation and prints the result to `stdout`. Each value is printed on its own +line. This is useful for inspecting objects without the extra annotations added +by [`util.inspect()`][] (such as `[Object: null prototype]`). + +```js +console.json({ foo: 'bar' }); +// Prints: { +// "foo": "bar" +// } + +// Works cleanly with null-prototype objects +const obj = Object.assign(Object.create(null), { foo: 'bar' }); +console.json(obj); +// Prints: { +// "foo": "bar" +// } +``` + +Non-JSON-serializable values (circular references, `undefined`, functions, +symbols) will throw a `TypeError`, unlike [`console.log()`][] which handles +them gracefully. Use [`util.inspect()`][] or [`console.dir()`][] for those +cases. + ### console.log([data][, ...args]) -* `...values` {any} +* `value` {any} +* `replacer` {Function|Array} Passed directly to [`JSON.stringify()`][]. +* `space` {number|string} Passed directly to [`JSON.stringify()`][]. -Serializes each argument as JSON using [`JSON.stringify()`][] with 2-space -indentation and prints the result to `stdout`. Each value is printed on its own -line. This is useful for inspecting objects without the extra annotations added -by [`util.inspect()`][] (such as `[Object: null prototype]`). +Prints `JSON.stringify(value, replacer, space)` to `stdout`. Arguments are +passed directly to [`JSON.stringify()`][], so the caller controls formatting. +This is useful for inspecting objects without the annotations added by +[`util.inspect()`][] (such as `[Object: null prototype]`). ```js console.json({ foo: 'bar' }); -// Prints: { +// Prints: {"foo":"bar"} + +console.json({ foo: 'bar' }, null, 2); +// Prints: +// { // "foo": "bar" // } // Works cleanly with null-prototype objects const obj = Object.assign(Object.create(null), { foo: 'bar' }); console.json(obj); -// Prints: { -// "foo": "bar" -// } +// Prints: {"foo":"bar"} ``` -Non-JSON-serializable values (circular references, `undefined`, functions, -symbols) will throw a `TypeError`, unlike [`console.log()`][] which handles -them gracefully. Use [`util.inspect()`][] or [`console.dir()`][] for those -cases. +Throws `TypeError` for non-serializable values such as circular references. +Use [`console.log()`][] or [`console.dir()`][] for values that may not be +JSON-serializable. ### console.log([data][, ...args]) -* `value` {any} -* `replacer` {Function|Array} Passed directly to [`JSON.stringify()`][]. -* `space` {number|string} Passed directly to [`JSON.stringify()`][]. +* `...values` {any} -Prints `JSON.stringify(value, replacer, space)` to `stdout`. Arguments are -passed directly to [`JSON.stringify()`][], so the caller controls formatting. -This is useful for inspecting objects without the annotations added by -[`util.inspect()`][] (such as `[Object: null prototype]`). +Serializes each argument with [`JSON.stringify()`][] and prints the result to +`stdout`. Each value is printed on its own line. This is useful for inspecting +objects without the annotations added by [`util.inspect()`][] (such as +`[Object: null prototype]`). ```js console.json({ foo: 'bar' }); // Prints: {"foo":"bar"} -console.json({ foo: 'bar' }, null, 2); -// Prints: -// { -// "foo": "bar" -// } - // Works cleanly with null-prototype objects const obj = Object.assign(Object.create(null), { foo: 'bar' }); console.json(obj); // Prints: {"foo":"bar"} + +// Multiple values printed on separate lines +console.json({ a: 1 }, { b: 2 }); +// Prints: {"a":1} +// {"b":2} ``` Throws `TypeError` for non-serializable values such as circular references. diff --git a/lib/internal/console/constructor.js b/lib/internal/console/constructor.js index c862f3a43e1e..1c108a54c2e8 100644 --- a/lib/internal/console/constructor.js +++ b/lib/internal/console/constructor.js @@ -298,7 +298,9 @@ const consoleMethods = { }, json(...args) { - this[kWriteToConsole](kUseStdout, JSON.stringify(...args)); + for (let i = 0; i < args.length; ++i) { + this[kWriteToConsole](kUseStdout, JSON.stringify(args[i])); + } }, time(label = 'default') { diff --git a/test/parallel/test-console-json.js b/test/parallel/test-console-json.js index 0a5f0a44acfb..d3d8b27024b5 100644 --- a/test/parallel/test-console-json.js +++ b/test/parallel/test-console-json.js @@ -18,7 +18,7 @@ function captured(fn) { return output; } -// Basic object — no indentation by default +// Basic object assert.strictEqual( captured(() => c.json({ foo: 'bar' })), '{"foo":"bar"}\n' @@ -31,16 +31,10 @@ assert.strictEqual( '{"foo":"bar"}\n' ); -// Caller controls indentation via JSON.stringify args +// Multiple args — each printed on a separate line assert.strictEqual( - captured(() => c.json({ foo: 'bar' }, null, 2)), - '{\n "foo": "bar"\n}\n' -); - -// Replacer array -assert.strictEqual( - captured(() => c.json({ foo: 'bar', baz: 1 }, ['foo'])), - '{"foo":"bar"}\n' + captured(() => c.json({ a: 1 }, { b: 2 })), + '{"a":1}\n{"b":2}\n' ); // Array From 6886458d9d9b215ed7872d41d5a9ede44ab53f9d Mon Sep 17 00:00:00 2001 From: Mark Larah Date: Wed, 12 Aug 2026 15:59:58 -0500 Subject: [PATCH 4/4] console: use explicit value/replacer/space params in console.json Match JSON.stringify's own signature rather than using rest args. Co-Authored-By: Claude --- doc/api/console.md | 21 ++++++++++++--------- lib/internal/console/constructor.js | 6 ++---- test/parallel/test-console-json.js | 15 +++++++++------ 3 files changed, 23 insertions(+), 19 deletions(-) diff --git a/doc/api/console.md b/doc/api/console.md index 7fcd0de6f844..691f54ad9e57 100644 --- a/doc/api/console.md +++ b/doc/api/console.md @@ -328,14 +328,16 @@ added: v0.1.100 The `console.info()` function is an alias for [`console.log()`][]. -### console.json(...values) +### console.json(value[, replacer[, space]]) -* `...values` {any} +* `value` {any} +* `replacer` {Function|Array|null} +* `space` {number|string} -Serializes each argument with [`JSON.stringify()`][] and prints the result to -`stdout`. Each value is printed on its own line. This is useful for inspecting +Prints `JSON.stringify(value, replacer, space)` to `stdout`. Arguments are +passed directly to [`JSON.stringify()`][]. This is useful for inspecting objects without the annotations added by [`util.inspect()`][] (such as `[Object: null prototype]`). @@ -343,15 +345,16 @@ objects without the annotations added by [`util.inspect()`][] (such as console.json({ foo: 'bar' }); // Prints: {"foo":"bar"} +console.json({ foo: 'bar' }, null, 2); +// Prints: +// { +// "foo": "bar" +// } + // Works cleanly with null-prototype objects const obj = Object.assign(Object.create(null), { foo: 'bar' }); console.json(obj); // Prints: {"foo":"bar"} - -// Multiple values printed on separate lines -console.json({ a: 1 }, { b: 2 }); -// Prints: {"a":1} -// {"b":2} ``` Throws `TypeError` for non-serializable values such as circular references. diff --git a/lib/internal/console/constructor.js b/lib/internal/console/constructor.js index 1c108a54c2e8..ece11f609aef 100644 --- a/lib/internal/console/constructor.js +++ b/lib/internal/console/constructor.js @@ -297,10 +297,8 @@ const consoleMethods = { })); }, - json(...args) { - for (let i = 0; i < args.length; ++i) { - this[kWriteToConsole](kUseStdout, JSON.stringify(args[i])); - } + json(value, replacer, space) { + this[kWriteToConsole](kUseStdout, JSON.stringify(value, replacer, space)); }, time(label = 'default') { diff --git a/test/parallel/test-console-json.js b/test/parallel/test-console-json.js index d3d8b27024b5..895d91d771e3 100644 --- a/test/parallel/test-console-json.js +++ b/test/parallel/test-console-json.js @@ -31,18 +31,21 @@ assert.strictEqual( '{"foo":"bar"}\n' ); -// Multiple args — each printed on a separate line +// With space for pretty-printing assert.strictEqual( - captured(() => c.json({ a: 1 }, { b: 2 })), - '{"a":1}\n{"b":2}\n' + captured(() => c.json({ foo: 'bar' }, null, 2)), + '{\n "foo": "bar"\n}\n' ); -// Array +// With replacer array assert.strictEqual( - captured(() => c.json([1, 2, 3])), - '[1,2,3]\n' + captured(() => c.json({ foo: 'bar', baz: 1 }, ['foo'])), + '{"foo":"bar"}\n' ); +// Array +assert.strictEqual(captured(() => c.json([1, 2, 3])), '[1,2,3]\n'); + // Primitives assert.strictEqual(captured(() => c.json(42)), '42\n'); assert.strictEqual(captured(() => c.json('hello')), '"hello"\n');