mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 15:08:46 +00:00
fix(console): implement %j format specifier for JSON output (#25195)
## Summary
- Implements the `%j` format specifier for `console.log` and related
console methods
- `%j` outputs the JSON stringified representation of the value
- Previously, `%j` was not recognized and was left as literal text in
the output
## Test plan
- [x] Run `bun bd test test/regression/issue/24234.test.ts` - all 5
tests pass
- [x] Verify tests fail with system Bun (`USE_SYSTEM_BUN=1`) to confirm
fix validity
- [x] Manual verification: `console.log('%j', {foo: 'bar'})` outputs
`{"foo":"bar"}`
## Example
Before (bug):
```
$ bun -e "console.log('%j %s', {foo: 'bar'}, 'hello')"
%j [object Object] hello
```
After (fixed):
```
$ bun -e "console.log('%j %s', {foo: 'bar'}, 'hello')"
{"foo":"bar"} hello
```
Closes #24234
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1425,6 +1425,7 @@ pub const Formatter = struct {
|
||||
o, // o
|
||||
O, // O
|
||||
c, // c
|
||||
j, // j
|
||||
};
|
||||
|
||||
fn writeWithFormatting(
|
||||
@@ -1466,6 +1467,7 @@ pub const Formatter = struct {
|
||||
'O' => .O,
|
||||
'd', 'i' => .i,
|
||||
'c' => .c,
|
||||
'j' => .j,
|
||||
'%' => {
|
||||
// print up to and including the first %
|
||||
const end = slice[0..i];
|
||||
@@ -1625,6 +1627,16 @@ pub const Formatter = struct {
|
||||
.c => {
|
||||
// TODO: Implement %c
|
||||
},
|
||||
|
||||
.j => {
|
||||
// JSON.stringify the value
|
||||
var str = bun.String.empty;
|
||||
defer str.deref();
|
||||
|
||||
try next_value.jsonStringify(global, 0, &str);
|
||||
this.addForNewLine(str.length());
|
||||
writer.print("{f}", .{str});
|
||||
},
|
||||
}
|
||||
if (this.remaining_values.len == 0) break;
|
||||
},
|
||||
|
||||
@@ -236,7 +236,7 @@ Hello World 123
|
||||
Hello %vWorld 123
|
||||
Hello NaN %i
|
||||
Hello NaN % 1
|
||||
Hello NaN %j 1
|
||||
Hello NaN 1
|
||||
Hello \5 6,
|
||||
Hello %i 5 6
|
||||
%d 1
|
||||
|
||||
72
test/regression/issue/24234.test.ts
Normal file
72
test/regression/issue/24234.test.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import { expect, test } from "bun:test";
|
||||
import { bunEnv, bunExe } from "harness";
|
||||
|
||||
test("console.log with %j should format as JSON", async () => {
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "-e", "console.log('%j', {foo: 'bar'})"],
|
||||
env: bunEnv,
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
expect(stderr).toBe("");
|
||||
expect(stdout).toBe('{"foo":"bar"}\n');
|
||||
expect(exitCode).toBe(0);
|
||||
});
|
||||
|
||||
test("console.log with %j should handle arrays", async () => {
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "-e", "console.log('%j', [1, 2, 3])"],
|
||||
env: bunEnv,
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
expect(stderr).toBe("");
|
||||
expect(stdout).toBe("[1,2,3]\n");
|
||||
expect(exitCode).toBe(0);
|
||||
});
|
||||
|
||||
test("console.log with %j should handle nested objects", async () => {
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "-e", "console.log('%j', {a: {b: {c: 123}}})"],
|
||||
env: bunEnv,
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
expect(stderr).toBe("");
|
||||
expect(stdout).toBe('{"a":{"b":{"c":123}}}\n');
|
||||
expect(exitCode).toBe(0);
|
||||
});
|
||||
|
||||
test("console.log with %j should handle primitives", async () => {
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "-e", "console.log('%j %j %j %j', 'string', 123, true, null)"],
|
||||
env: bunEnv,
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
expect(stderr).toBe("");
|
||||
expect(stdout).toBe('"string" 123 true null\n');
|
||||
expect(exitCode).toBe(0);
|
||||
});
|
||||
|
||||
test("console.log with %j and additional text", async () => {
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "-e", "console.log('Result: %j', {status: 'ok'})"],
|
||||
env: bunEnv,
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
expect(stderr).toBe("");
|
||||
expect(stdout).toBe('Result: {"status":"ok"}\n');
|
||||
expect(exitCode).toBe(0);
|
||||
});
|
||||
Reference in New Issue
Block a user