From 376aad87d69afa13f4cb8854e67e594ede84a2c4 Mon Sep 17 00:00:00 2001 From: MarkXian Date: Thu, 23 Jul 2026 12:03:57 +0800 Subject: [PATCH 1/2] fix(formatter): stringify bigint values in json output --- .changeset/brown-glasses-thank.md | 5 +++++ packages/effect/src/Formatter.ts | 10 +++++++--- packages/effect/test/Formatter.test.ts | 18 +++++++++++++++++- 3 files changed, 29 insertions(+), 4 deletions(-) create mode 100644 .changeset/brown-glasses-thank.md diff --git a/.changeset/brown-glasses-thank.md b/.changeset/brown-glasses-thank.md new file mode 100644 index 00000000000..269c6616347 --- /dev/null +++ b/.changeset/brown-glasses-thank.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Handle BigInt values safely when formatting JSON diagnostics. diff --git a/packages/effect/src/Formatter.ts b/packages/effect/src/Formatter.ts index 426131f10ba..d9a6a0a8029 100644 --- a/packages/effect/src/Formatter.ts +++ b/packages/effect/src/Formatter.ts @@ -253,9 +253,10 @@ function safeToString(input: any): string { * Uses `JSON.stringify` internally with a replacer that tracks the current * object ancestry. Circular references are replaced with `undefined`, which * omits them from object output. `Redactable` values are automatically redacted - * before serialization. Values not supported by JSON, such as `BigInt`, - * `Symbol`, `undefined`, and functions, follow standard `JSON.stringify` - * behavior. The `space` parameter controls indentation and defaults to `0`. + * before serialization. `BigInt` values are stringified with an `n` suffix. + * Other values not supported by JSON, such as `Symbol`, `undefined`, and + * functions, follow standard `JSON.stringify` behavior. The `space` parameter + * controls indentation and defaults to `0`. * * **Example** (Formatting compact JSON) * @@ -302,6 +303,9 @@ export function formatJson(input: unknown, options?: { input, function(this: unknown, _key: string, value: unknown) { const redacted = redact(value) + if (typeof redacted === "bigint") { + return `${redacted}n` + } if (typeof redacted !== "object" || redacted === null) { return redacted } diff --git a/packages/effect/test/Formatter.test.ts b/packages/effect/test/Formatter.test.ts index 782dcedb2dc..aea0f4d2e83 100644 --- a/packages/effect/test/Formatter.test.ts +++ b/packages/effect/test/Formatter.test.ts @@ -1,4 +1,4 @@ -import { Context, Option, Redactable, Redacted, Schema } from "effect" +import { Context, Inspectable, Option, Redactable, Redacted, Schema } from "effect" import { format, formatJson } from "effect/Formatter" import { describe, it } from "vitest" import { strictEqual } from "./utils/assert.ts" @@ -239,9 +239,25 @@ describe("Formatter", () => { strictEqual(formatJson({ left: shared, right: shared }), `{"left":{"a":1},"right":{"a":1}}`) }) + it("should stringify BigInt values", () => { + strictEqual(formatJson({ value: 123n }), `{"value":"123n"}`) + strictEqual(formatJson([1n, 2n]), `["1n","2n"]`) + }) + it("should redact sensitive data", () => { strictEqual(formatJson(data), `{"secret":"[REDACTED]"}`) strictEqual(formatJson({ a: data }), `{"a":{"secret":"[REDACTED]"}}`) }) }) + + describe("Inspectable.toStringUnknown", () => { + it("should stringify BigInt values", () => { + strictEqual( + Inspectable.toStringUnknown({ value: 123n }), + `{ + "value": "123n" +}` + ) + }) + }) }) From 01051fef13ae027d2c640351b2e752288877e77c Mon Sep 17 00:00:00 2001 From: Giulio Canti Date: Tue, 11 Aug 2026 09:14:46 +0200 Subject: [PATCH 2/2] Make BigInt formatting consistent --- .changeset/brown-glasses-thank.md | 2 +- packages/effect/src/Formatter.ts | 2 +- packages/effect/src/Inspectable.ts | 2 +- packages/effect/src/internal/effect.ts | 1 - packages/effect/test/Formatter.test.ts | 2 ++ packages/effect/test/Logger.test.ts | 21 +++++++++++++++++++++ 6 files changed, 26 insertions(+), 4 deletions(-) diff --git a/.changeset/brown-glasses-thank.md b/.changeset/brown-glasses-thank.md index 269c6616347..0445b02de0b 100644 --- a/.changeset/brown-glasses-thank.md +++ b/.changeset/brown-glasses-thank.md @@ -2,4 +2,4 @@ "effect": patch --- -Handle BigInt values safely when formatting JSON diagnostics. +Handle BigInt values safely and consistently across JSON diagnostics and logger formats. diff --git a/packages/effect/src/Formatter.ts b/packages/effect/src/Formatter.ts index 9486ac83633..fb73d19c79c 100644 --- a/packages/effect/src/Formatter.ts +++ b/packages/effect/src/Formatter.ts @@ -301,7 +301,7 @@ export function formatJson(input: unknown, options?: { function(this: unknown, _key: string, value: unknown) { const redacted = redact(value) if (typeof redacted === "bigint") { - return `${redacted}n` + return format(redacted) } if (typeof redacted !== "object" || redacted === null) { return redacted diff --git a/packages/effect/src/Inspectable.ts b/packages/effect/src/Inspectable.ts index e2c06932a2c..ddf3e270108 100644 --- a/packages/effect/src/Inspectable.ts +++ b/packages/effect/src/Inspectable.ts @@ -187,7 +187,7 @@ export const toStringUnknown = (u: unknown, whitespace: number | string | undefi return u } try { - return typeof u === "object" ? formatJson(u, { space: whitespace }) : String(u) + return typeof u === "object" ? formatJson(u, { space: whitespace }) : format(u, { space: whitespace }) } catch { return String(u) } diff --git a/packages/effect/src/internal/effect.ts b/packages/effect/src/internal/effect.ts index 370b36ec222..99c3632ff8a 100644 --- a/packages/effect/src/internal/effect.ts +++ b/packages/effect/src/internal/effect.ts @@ -6302,7 +6302,6 @@ export const formatLogSpan = (self: [label: string, timestamp: number], now: num /** @internal */ export const structuredMessage = (u: unknown): unknown => { switch (typeof u) { - case "bigint": case "function": case "symbol": { return String(u) diff --git a/packages/effect/test/Formatter.test.ts b/packages/effect/test/Formatter.test.ts index 3f26c04dc1d..a862bff56d6 100644 --- a/packages/effect/test/Formatter.test.ts +++ b/packages/effect/test/Formatter.test.ts @@ -258,6 +258,7 @@ describe("Formatter", () => { }) it("should stringify BigInt values", () => { + strictEqual(formatJson(123n), `"123n"`) strictEqual(formatJson({ value: 123n }), `{"value":"123n"}`) strictEqual(formatJson([1n, 2n]), `["1n","2n"]`) }) @@ -270,6 +271,7 @@ describe("Formatter", () => { describe("Inspectable.toStringUnknown", () => { it("should stringify BigInt values", () => { + strictEqual(Inspectable.toStringUnknown(123n), `123n`) strictEqual( Inspectable.toStringUnknown({ value: 123n }), `{ diff --git a/packages/effect/test/Logger.test.ts b/packages/effect/test/Logger.test.ts index 5871e875217..3d54139c300 100644 --- a/packages/effect/test/Logger.test.ts +++ b/packages/effect/test/Logger.test.ts @@ -90,6 +90,27 @@ describe("Logger", () => { assert.ok(!output.includes("annotation=\"\\\"value with spaces\\\"\"")) })) + it.effect("formats BigInt messages consistently", () => + Effect.gen(function*() { + const simple: Array = [] + const logFmt: Array = [] + const structured: Array = [] + const json: Array = [] + const loggers = [ + Logger.formatSimple.pipe(Logger.map((output) => void simple.push(output))), + Logger.formatLogFmt.pipe(Logger.map((output) => void logFmt.push(output))), + Logger.formatStructured.pipe(Logger.map((output) => void structured.push(output.message))), + Logger.formatJson.pipe(Logger.map((output) => void json.push(JSON.parse(output).message))) + ] + + yield* Effect.logInfo(123n, { value: 123n }).pipe(Effect.provide(Logger.layer(loggers))) + + assert.include(simple[0], `message=123n message="{\\"value\\":123n}"`) + assert.include(logFmt[0], `message=123n message="{\\"value\\":123n}"`) + assert.deepStrictEqual(structured, [[123n, { value: 123n }]]) + assert.deepStrictEqual(json, [["123n", { value: "123n" }]]) + })) + it.effect("annotateLogsScoped applies annotations only while scoped", () => Effect.gen(function*() { const annotations: Array> = []