Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions src/url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 to prevent double encoding in recursive calls
const nestedQuery = stringifyRecursive(value, keyPath, depth + 1);

// Only append if nestedQuery has content
if (!nestedQuery) {
Expand All @@ -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
? ""
Expand Down
23 changes: 23 additions & 0 deletions tests/url.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]");
});
});