| author | |
| committer | |
| log | 32407df9c5ffd3c5dda2e745568997ac33c15ed1 |
| tree | daf2abf5312b3c8ba744ed5f97e0ac5cd08dd7e1 |
| parent | 162e774294f9e3dda15ca2f8083db019fe398837 |
| signature | Signed by PGP key B5690EEEBB952194 |
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.`), | ... | @@ -57,6 +57,15 @@ Done.`), |
| 57 | expect(() => decodeLenientJson('{ "enabled": true,, }')).toThrow(); | 57 | expect(() => decodeLenientJson('{ "enabled": true,, }')).toThrow(); |
| 58 | }); | 58 | }); |
| 59 | 59 | ||
| 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 | |||
| 60 | it("formats schema failures with paths without exposing invalid values", () => { | 69 | it("formats schema failures with paths without exposing invalid values", () => { |
| 61 | const decodeCredential = decodeJsonResult(Schema.Struct({ token: Schema.Number })); | 70 | const decodeCredential = decodeJsonResult(Schema.Struct({ token: Schema.Number })); |
| 62 | const decoded = decodeCredential('{"token":"credential=secret-value"}'); | 71 | const decoded = decodeCredential('{"token":"credential=secret-value"}'); |
packages/shared/src/schemaJson.ts+8-2| ... | @@ -190,8 +190,14 @@ const parseLenientJsonGetter = SchemaGetter.onSome((input: string) => { | ... | @@ -190,8 +190,14 @@ const parseLenientJsonGetter = SchemaGetter.onSome((input: string) => { |
| 190 | (match, stringLiteral: string | undefined) => (stringLiteral ? match : ""), | 190 | (match, stringLiteral: string | undefined) => (stringLiteral ? match : ""), |
| 191 | ); | 191 | ); |
| 192 | 192 | ||
| 193 | // Strip trailing commas before `}` or `]`. | 193 | // Strip trailing commas before `}` or `]`. The alternation preserves quoted |
| 194 | stripped = stripped.replace(/,(\s*[}\]])/g, "$1"); | 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 | ); | ||
| 195 | 201 | ||
| 196 | return decodeJsonString(stripped).pipe( | 202 | return decodeJsonString(stripped).pipe( |
| 197 | Effect.map(Option.some), | 203 | Effect.map(Option.some), |