From 9e2ac7bed85c3489697204aba3d2f4b718622b93 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 16 Jan 2026 09:08:06 +0000 Subject: [PATCH 1/3] Initial plan From 48e701878efaff3a3c3265bac1ace833b9aea1b5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 16 Jan 2026 09:12:20 +0000 Subject: [PATCH 2/3] fix: prevent double encoding in nested query params at 3+ levels Co-authored-by: bnachtweh <32386227+bnachtweh@users.noreply.github.com> --- src/url.ts | 10 ++++++---- tests/url.spec.ts | 23 +++++++++++++++++++++++ 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/url.ts b/src/url.ts index 425c7e9..d025c62 100644 --- a/src/url.ts +++ b/src/url.ts @@ -31,13 +31,13 @@ function stringify( return queryString; } - const encodedKey = prefix - ? encodeURIComponent(`${prefix}[${key}]`) - : encodeURIComponent(key); + // Build the unencoded key path first + const keyPath = prefix ? `${prefix}[${key}]` : key; // Check if value is an object and we haven't exceeded max depth if (typeof value === "object" && value !== null && depth < maxDepth) { - const nestedQuery = stringifyRecursive(value, encodedKey, depth + 1); + // Pass unencoded keyPath as prefix for recursion + const nestedQuery = stringifyRecursive(value, keyPath, depth + 1); // Only append if nestedQuery has content if (!nestedQuery) { @@ -48,6 +48,8 @@ function stringify( ? `${queryString}${separator}${nestedQuery}` : nestedQuery; } else { + // Encode only once at the leaf level + const encodedKey = encodeURIComponent(keyPath); const encodedValue = typeof value === "object" && value !== null ? "" diff --git a/tests/url.spec.ts b/tests/url.spec.ts index 7561530..1040188 100644 --- a/tests/url.spec.ts +++ b/tests/url.spec.ts @@ -195,4 +195,27 @@ describe("DynamicURL", () => { expect(result).toBe("https://example.com?citizen=robespierre"); expect(result).not.toContain("emptyNested"); }); + + test("should not double-encode nested objects at 3+ levels deep", () => { + const url = new DynamicURL("https://example.com"); + url.setQueryParams({ + level1: { + level2: { + level3: "value", + }, + }, + }); + + const result = url.resolve(); + // Should have single encoding: level1[level2][level3] + expect(result).toBe( + "https://example.com?level1%5Blevel2%5D%5Blevel3%5D=value" + ); + + // Verify decoded key is correct + const queryString = result.split("?")[1]; + const params = new URLSearchParams(queryString); + const keys = Array.from(params.keys()); + expect(keys[0]).toBe("level1[level2][level3]"); + }); }); From 3476edc96e7abdb84e6385cfe9f4bffd1071749f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 16 Jan 2026 09:13:31 +0000 Subject: [PATCH 3/3] docs: improve comment clarity for double encoding prevention Co-authored-by: bnachtweh <32386227+bnachtweh@users.noreply.github.com> --- src/url.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/url.ts b/src/url.ts index d025c62..4738ac9 100644 --- a/src/url.ts +++ b/src/url.ts @@ -36,7 +36,7 @@ function stringify( // Check if value is an object and we haven't exceeded max depth if (typeof value === "object" && value !== null && depth < maxDepth) { - // Pass unencoded keyPath as prefix for recursion + // Pass unencoded keyPath as prefix to prevent double encoding in recursive calls const nestedQuery = stringifyRecursive(value, keyPath, depth + 1); // Only append if nestedQuery has content