From a339d02aa4505de4f416a088d3ccf16c3a8626eb Mon Sep 17 00:00:00 2001 From: Rupayon Haldar <80724680+rupayon123@users.noreply.github.com> Date: Mon, 15 Jun 2026 13:59:48 -0400 Subject: [PATCH 1/6] fix: distinguish malformed JSON from partial arrays --- src/index.ts | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/index.ts b/src/index.ts index ea6977d..70831a0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -35,6 +35,8 @@ const _parseJSON = (jsonString: string, allow: number) => { throw new MalformedJSON(`${msg} at position ${index}`); }; + const isPartialJSON = (e: unknown) => e instanceof PartialJSON; + const parseAny: () => any = () => { skipBlank(); if (index >= length) markPartialJSON("Unexpected end of input"); @@ -108,15 +110,18 @@ const _parseJSON = (jsonString: string, allow: number) => { const value = parseAny(); obj[key] = value; } catch (e) { - if (Allow.OBJ & allow) return obj; - else throw e; + if (Allow.OBJ & allow && isPartialJSON(e)) return obj; + throw e; } skipBlank(); if (jsonString[index] === ",") index++; // skip comma } } catch (e) { - if (Allow.OBJ & allow) return obj; - else markPartialJSON("Expected '}' at end of object"); + if (isPartialJSON(e)) { + if (Allow.OBJ & allow) return obj; + markPartialJSON("Expected '}' at end of object"); + } + throw e; } index++; // skip final brace return obj; @@ -124,6 +129,7 @@ const _parseJSON = (jsonString: string, allow: number) => { const parseArr = () => { index++; // skip initial bracket + skipBlank(); const arr = []; try { while (jsonString[index] !== "]") { @@ -131,13 +137,17 @@ const _parseJSON = (jsonString: string, allow: number) => { skipBlank(); if (jsonString[index] === ",") { index++; // skip comma + skipBlank(); } } } catch (e) { - if (Allow.ARR & allow) { - return arr; + if (isPartialJSON(e)) { + if (Allow.ARR & allow) { + return arr; + } + markPartialJSON("Expected ']' at end of array"); } - markPartialJSON("Expected ']' at end of array"); + throw e; } index++; // skip final bracket return arr; From 1a9d22d8228a8537bd2fee2b3e45b6a55c6eea23 Mon Sep 17 00:00:00 2001 From: Rupayon Haldar <80724680+rupayon123@users.noreply.github.com> Date: Mon, 15 Jun 2026 13:59:57 -0400 Subject: [PATCH 2/6] test: cover malformed array parsing --- src/index.test.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 src/index.test.ts diff --git a/src/index.test.ts b/src/index.test.ts new file mode 100644 index 0000000..baec682 --- /dev/null +++ b/src/index.test.ts @@ -0,0 +1,16 @@ +import { describe, expect, it } from "vitest"; +import { MalformedJSON, parse } from "./index"; + +describe("malformed JSON handling", () => { + it("throws for malformed numbers inside arrays", () => { + expect(() => parse("[1, .05, 2]")).toThrow(MalformedJSON); + }); + + it("keeps parsing after an empty array with spaces", () => { + expect(parse('[{"id":1,"arr":["hello"]},{"id":2,"arr":[ ],"more":"yaya"},{"id":3,"arr":["!"]}]')).toEqual([ + { id: 1, arr: ["hello"] }, + { id: 2, arr: [], more: "yaya" }, + { id: 3, arr: ["!"] }, + ]); + }); +}); From 895233c0a43f3bcf9c07199ec7b9cba01760a6d7 Mon Sep 17 00:00:00 2001 From: Rupayon Haldar <80724680+rupayon123@users.noreply.github.com> Date: Tue, 16 Jun 2026 16:06:10 -0400 Subject: [PATCH 3/6] Address malformed JSON review feedback --- src/index.ts | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/index.ts b/src/index.ts index 70831a0..818aee4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -114,14 +114,15 @@ const _parseJSON = (jsonString: string, allow: number) => { throw e; } skipBlank(); - if (jsonString[index] === ",") index++; // skip comma + if (jsonString[index] === ",") { + index++; // skip comma + skipBlank(); + } } } catch (e) { - if (isPartialJSON(e)) { - if (Allow.OBJ & allow) return obj; - markPartialJSON("Expected '}' at end of object"); - } - throw e; + if (!isPartialJSON(e)) throw e; + if (Allow.OBJ & allow) return obj; + markPartialJSON("Expected '}' at end of object"); } index++; // skip final brace return obj; @@ -141,13 +142,11 @@ const _parseJSON = (jsonString: string, allow: number) => { } } } catch (e) { - if (isPartialJSON(e)) { - if (Allow.ARR & allow) { - return arr; - } - markPartialJSON("Expected ']' at end of array"); + if (!isPartialJSON(e)) throw e; + if (Allow.ARR & allow) { + return arr; } - throw e; + markPartialJSON("Expected ']' at end of array"); } index++; // skip final bracket return arr; From ddbf63f453c02927e90763b2ce6a7648b2164b61 Mon Sep 17 00:00:00 2001 From: Rupayon Haldar <80724680+rupayon123@users.noreply.github.com> Date: Tue, 16 Jun 2026 16:06:11 -0400 Subject: [PATCH 4/6] Add malformed object JSON tests --- src/index.test.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/index.test.ts b/src/index.test.ts index baec682..7dbe6f2 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -6,6 +6,10 @@ describe("malformed JSON handling", () => { expect(() => parse("[1, .05, 2]")).toThrow(MalformedJSON); }); + it("throws for malformed numbers inside objects", () => { + expect(() => parse('{"a": .05, "b": 2}')).toThrow(MalformedJSON); + }); + it("keeps parsing after an empty array with spaces", () => { expect(parse('[{"id":1,"arr":["hello"]},{"id":2,"arr":[ ],"more":"yaya"},{"id":3,"arr":["!"]}]')).toEqual([ { id: 1, arr: ["hello"] }, @@ -13,4 +17,8 @@ describe("malformed JSON handling", () => { { id: 3, arr: ["!"] }, ]); }); + + it("keeps parsing after an object comma with spaces", () => { + expect(parse('{"a":1, }')).toEqual({ a: 1 }); + }); }); From 3ae30e4f8a736b4f1f54b2d8b767ddb562ca9bca Mon Sep 17 00:00:00 2001 From: Rupayon Haldar <80724680+rupayon123@users.noreply.github.com> Date: Fri, 19 Jun 2026 16:35:11 -0400 Subject: [PATCH 5/6] test: cover array comma whitespace --- src/index.test.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/index.test.ts b/src/index.test.ts index 7dbe6f2..0d6f7a8 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -21,4 +21,8 @@ describe("malformed JSON handling", () => { it("keeps parsing after an object comma with spaces", () => { expect(parse('{"a":1, }')).toEqual({ a: 1 }); }); + + it("keeps parsing after an array comma with spaces", () => { + expect(parse("[1, ]")).toEqual([1]); + }); }); From ca2b1c4908bb68d59f71528da91bf054c0291710 Mon Sep 17 00:00:00 2001 From: Rupayon Haldar <80724680+rupayon123@users.noreply.github.com> Date: Wed, 1 Jul 2026 03:30:55 -0400 Subject: [PATCH 6/6] fix: preserve partial object errors at EOF --- src/index.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 818aee4..3a56a70 100644 --- a/src/index.ts +++ b/src/index.ts @@ -120,7 +120,10 @@ const _parseJSON = (jsonString: string, allow: number) => { } } } catch (e) { - if (!isPartialJSON(e)) throw e; + if (!isPartialJSON(e)) { + if (index >= length) markPartialJSON("Expected '}' at end of object"); + throw e; + } if (Allow.OBJ & allow) return obj; markPartialJSON("Expected '}' at end of object"); }