authorgravatar for 132888838+arhxam@users.noreply.github.comArham Amin <132888838+arhxam@users.noreply.github.com> 2026-07-31 03:46:22+05:30
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-07-30 22:16:22+00:00
log32407df9c5ffd3c5dda2e745568997ac33c15ed1
treedaf2abf5312b3c8ba744ed5f97e0ac5cd08dd7e1
parent162e774294f9e3dda15ca2f8083db019fe398837
signature Signed by PGP key B5690EEEBB952194

fix(shared): lenient JSON parser deletes commas inside string values (#5025)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

2 files changed, 17 insertions(+), 2 deletions(-)

packages/shared/src/schemaJson.test.ts+9
......@@ -57,6 +57,15 @@ Done.`),
5757 expect(() => decodeLenientJson('{ "enabled": true,, }')).toThrow();
5858 });
5959
60 it("preserves commas before brackets inside string values", () => {
61 // A comma inside a string value that happens to precede `}`/`]` must not
62 // be stripped as if it were a trailing comma.
63 expect(decodeLenientJson('{"note":"a,]"}')).toEqual({ note: "a,]" });
64 expect(decodeLenientJson('{"list":["x,}"]}')).toEqual({ list: ["x,}"] });
65 // Genuine trailing commas are still removed.
66 expect(decodeLenientJson('{"values":[1, 2,],}')).toEqual({ values: [1, 2] });
67 });
68
6069 it("formats schema failures with paths without exposing invalid values", () => {
6170 const decodeCredential = decodeJsonResult(Schema.Struct({ token: Schema.Number }));
6271 const decoded = decodeCredential('{"token":"credential=secret-value"}');
packages/shared/src/schemaJson.ts+8-2
......@@ -190,8 +190,14 @@ const parseLenientJsonGetter = SchemaGetter.onSome((input: string) => {
190190 (match, stringLiteral: string | undefined) => (stringLiteral ? match : ""),
191191 );
192192
193 // Strip trailing commas before `}` or `]`.
194 stripped = stripped.replace(/,(\s*[}\]])/g, "$1");
193 // Strip trailing commas before `}` or `]`. The alternation preserves quoted
194 // strings so a comma inside a string value (e.g. `{"note":"a,]"}`) is not
195 // mistaken for a trailing comma and removed.
196 stripped = stripped.replace(
197 /("(?:[^"\\]|\\.)*")|,(\s*[}\]])/g,
198 (match, stringLiteral: string | undefined, bracket: string | undefined) =>
199 stringLiteral ? match : (bracket ?? ""),
200 );
195201
196202 return decodeJsonString(stripped).pipe(
197203 Effect.map(Option.some),