From 32407df9c5ffd3c5dda2e745568997ac33c15ed1 Mon Sep 17 00:00:00 2001 From: Arham Amin <132888838+arhxam@users.noreply.github.com> Date: Fri, 31 Jul 2026 03:46:22 +0530 Subject: [PATCH] fix(shared): lenient JSON parser deletes commas inside string values (#5025) Co-authored-by: Claude Opus 4.8 (1M context) --- packages/shared/src/schemaJson.test.ts | 9 +++++++++ packages/shared/src/schemaJson.ts | 10 ++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/packages/shared/src/schemaJson.test.ts b/packages/shared/src/schemaJson.test.ts index c808a9b7c515104f9cf63fc2e45aac3ede20b5be..4a4d16da0b30440c17d02cc134ae5c097e7a713e 100644 --- a/packages/shared/src/schemaJson.test.ts +++ b/packages/shared/src/schemaJson.test.ts @@ -57,6 +57,15 @@ Done.`), expect(() => decodeLenientJson('{ "enabled": true,, }')).toThrow(); }); + it("preserves commas before brackets inside string values", () => { + // A comma inside a string value that happens to precede `}`/`]` must not + // be stripped as if it were a trailing comma. + expect(decodeLenientJson('{"note":"a,]"}')).toEqual({ note: "a,]" }); + expect(decodeLenientJson('{"list":["x,}"]}')).toEqual({ list: ["x,}"] }); + // Genuine trailing commas are still removed. + expect(decodeLenientJson('{"values":[1, 2,],}')).toEqual({ values: [1, 2] }); + }); + it("formats schema failures with paths without exposing invalid values", () => { const decodeCredential = decodeJsonResult(Schema.Struct({ token: Schema.Number })); const decoded = decodeCredential('{"token":"credential=secret-value"}'); diff --git a/packages/shared/src/schemaJson.ts b/packages/shared/src/schemaJson.ts index 04d26d9c2293f93f5ae1b1d419d84ce7f7e51d67..77b1fa5d548d59acb564098d74ef78fbfaf738db 100644 --- a/packages/shared/src/schemaJson.ts +++ b/packages/shared/src/schemaJson.ts @@ -190,8 +190,14 @@ const parseLenientJsonGetter = SchemaGetter.onSome((input: string) => { (match, stringLiteral: string | undefined) => (stringLiteral ? match : ""), ); - // Strip trailing commas before `}` or `]`. - stripped = stripped.replace(/,(\s*[}\]])/g, "$1"); + // Strip trailing commas before `}` or `]`. The alternation preserves quoted + // strings so a comma inside a string value (e.g. `{"note":"a,]"}`) is not + // mistaken for a trailing comma and removed. + stripped = stripped.replace( + /("(?:[^"\\]|\\.)*")|,(\s*[}\]])/g, + (match, stringLiteral: string | undefined, bracket: string | undefined) => + stringLiteral ? match : (bracket ?? ""), + ); return decodeJsonString(stripped).pipe( Effect.map(Option.some), -- 2.54.0