From 3b15ce529d0372bd7f43e97e10815b912ddd4416 Mon Sep 17 00:00:00 2001 From: jin-sir <942725119@qq.com> Date: Wed, 17 Jun 2026 20:49:36 +0800 Subject: [PATCH 1/2] feat: add array type support for query parameters --- src/generateUrlWithQuery/__test__/index.test.ts | 10 ++++++++++ src/generateUrlWithQuery/index.ts | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/generateUrlWithQuery/__test__/index.test.ts b/src/generateUrlWithQuery/__test__/index.test.ts index b35779f..2df113b 100644 --- a/src/generateUrlWithQuery/__test__/index.test.ts +++ b/src/generateUrlWithQuery/__test__/index.test.ts @@ -55,4 +55,14 @@ describe('generateUrlWithQuery', () => { generateUrlWithQuery('/api/users', { obj: circularObj } as Record) ).toBe('/api/users'); }); + + test('handles array type parameters', () => { + const params = { + ids: [1, 2, 3], + tags: ['a', 'b'], + } as Record; + expect(generateUrlWithQuery('/api/users', params)).toBe( + '/api/users?ids=1%2C2%2C3&tags=a%2Cb' + ); + }); }); diff --git a/src/generateUrlWithQuery/index.ts b/src/generateUrlWithQuery/index.ts index 73ba6e8..fe77505 100644 --- a/src/generateUrlWithQuery/index.ts +++ b/src/generateUrlWithQuery/index.ts @@ -44,7 +44,7 @@ const generateUrlWithQuery = (pathname: string, queryParams: QueryParams = {}): const params = new URLSearchParams(); Object.entries(filteredParams).forEach(([key, value]) => { - if (['string', 'number', 'boolean'].includes(getTypeOfValue(value))) { + if (['string', 'number', 'boolean', 'array'].includes(getTypeOfValue(value))) { params.append(key, String(value)); } }); From 6f6810b4a57e4fb6ce49c0c29e3acbe327384553 Mon Sep 17 00:00:00 2001 From: jin-sir <942725119@qq.com> Date: Thu, 18 Jun 2026 14:44:49 +0800 Subject: [PATCH 2/2] feat: add array type support to generateUrlWithQuery - Add any[] to QueryValue type definition - Add tests for empty, nested, mixed-type, and complex arrays - Update JSDoc examples with array usage scenarios - Filter out empty string values from array conversion --- docs/api/functions/generateUrlWithQuery.md | 18 +++++- .../__test__/index.test.ts | 62 +++++++++++++++++++ src/generateUrlWithQuery/index.ts | 21 +++++-- 3 files changed, 94 insertions(+), 7 deletions(-) diff --git a/docs/api/functions/generateUrlWithQuery.md b/docs/api/functions/generateUrlWithQuery.md index 963bff4..7508295 100644 --- a/docs/api/functions/generateUrlWithQuery.md +++ b/docs/api/functions/generateUrlWithQuery.md @@ -4,7 +4,7 @@ > **generateUrlWithQuery**(`pathname`, `queryParams`): `string` -Defined in: [generateUrlWithQuery/index.ts:32](https://github.com/DTStack/dt-utils/blob/master/src/generateUrlWithQuery/index.ts#L32) +Defined in: [generateUrlWithQuery/index.ts:44](https://github.com/DTStack/dt-utils/blob/master/src/generateUrlWithQuery/index.ts#L44) 生成带查询参数的 URL @@ -42,8 +42,20 @@ import { generateUrlWithQuery } from 'dt-utils'; generateUrlWithQuery('/api/users', { id: 123 }) // => '/api/users?id=123' // 多个参数 -generateUrlWithQuery('/search', { q: 'test', page: 1, sort: 'desc' }) // => '/search?q=test&page=1&sort=desc' +generateUrlWithQuery('/search', { q: 'test', page: 1, sort: 'desc', tags: ['active', 'user'] }) // => '/search?q=test&page=1&sort=desc&tags=active,user' // 处理无效值 -generateUrlWithQuery('/api/data', { id: 123, name: null, status: undefined }) // => '/api/data?id=123' +generateUrlWithQuery('/api/data', { id: 123, name: null, status: undefined, empty: '', emptyArray: [] }) // => '/api/data?id=123' + +// 数组类型参数 +generateUrlWithQuery('/api/users', { ids: [1, 2, 3], tags: ['a', 'b'] }) // => '/api/users?ids=1,2,3&tags=a,b' + +// 嵌套数组(会被扁平化) +generateUrlWithQuery('/api/data', { matrix: [[1, 2], [3, 4]] }) // => '/api/data?matrix=1,2,3,4' + +// 混合类型数组 +generateUrlWithQuery('/api/data', { mixed: [1, 'two', true, null, undefined] }) // => '/api/data?mixed=1,two,true,,' + +// 复杂类型数组(使用 toString() 转换) +generateUrlWithQuery('/api/data', { objects: [{ a: 1 }, { b: 2 }] }) // => '/api/data?objects=[object Object],[object Object]' ``` diff --git a/src/generateUrlWithQuery/__test__/index.test.ts b/src/generateUrlWithQuery/__test__/index.test.ts index 2df113b..87ef6ed 100644 --- a/src/generateUrlWithQuery/__test__/index.test.ts +++ b/src/generateUrlWithQuery/__test__/index.test.ts @@ -65,4 +65,66 @@ describe('generateUrlWithQuery', () => { '/api/users?ids=1%2C2%2C3&tags=a%2Cb' ); }); + + test('handles empty array', () => { + const params = { + ids: [], + name: 'test', + } as Record; + // Empty array converts to empty string, which gets filtered out + expect(generateUrlWithQuery('/api/users', params)).toBe('/api/users?name=test'); + }); + + test('handles nested array', () => { + const params = { + matrix: [ + [1, 2], + [3, 4], + ], + tags: [ + ['a', 'b'], + ['c', 'd'], + ], + } as Record; + expect(generateUrlWithQuery('/api/data', params)).toBe( + '/api/data?matrix=1%2C2%2C3%2C4&tags=a%2Cb%2Cc%2Cd' + ); + }); + + test('handles mixed type array', () => { + const params = { + mixed: [1, 'two', true, null, undefined], + numbers: [1, 2.5, -3], + } as Record; + expect(generateUrlWithQuery('/api/data', params)).toBe( + '/api/data?mixed=1%2Ctwo%2Ctrue%2C%2C&numbers=1%2C2.5%2C-3' + ); + }); + + test('handles complex type array', () => { + // Create objects with custom toString for predictable output + class CustomObj { + value: number; + constructor(value: number) { + this.value = value; + } + toString() { + return `Custom(${this.value})`; + } + } + + const params = { + objects: [new CustomObj(1), new CustomObj(2)], + simpleObjects: [{ a: 1 }, { b: 2 }], + } as Record; + + // Expected URL encoding: + // Custom(1) -> Custom%281%29 + // Custom(2) -> Custom%282%29 + // comma -> %2C + // [object Object] -> %5Bobject+Object%5D + expect(generateUrlWithQuery('/api/data', params)).toBe( + '/api/data?objects=Custom%281%29%2CCustom%282%29&simpleObjects=%5Bobject+Object%5D%2C%5Bobject+Object%5D' + ); + }); }); diff --git a/src/generateUrlWithQuery/index.ts b/src/generateUrlWithQuery/index.ts index fe77505..dd216f6 100644 --- a/src/generateUrlWithQuery/index.ts +++ b/src/generateUrlWithQuery/index.ts @@ -1,6 +1,6 @@ import getTypeOfValue from '../getTypeOfValue'; -type QueryValue = string | number | boolean | null | undefined; +type QueryValue = string | number | boolean | any[] | null | undefined; type QueryParams = Record; /** @@ -23,10 +23,22 @@ type QueryParams = Record; * generateUrlWithQuery('/api/users', { id: 123 }) // => '/api/users?id=123' * * // 多个参数 - * generateUrlWithQuery('/search', { q: 'test', page: 1, sort: 'desc' }) // => '/search?q=test&page=1&sort=desc' + * generateUrlWithQuery('/search', { q: 'test', page: 1, sort: 'desc', tags: ['active', 'user'] }) // => '/search?q=test&page=1&sort=desc&tags=active,user' * * // 处理无效值 - * generateUrlWithQuery('/api/data', { id: 123, name: null, status: undefined }) // => '/api/data?id=123' + * generateUrlWithQuery('/api/data', { id: 123, name: null, status: undefined, empty: '', emptyArray: [] }) // => '/api/data?id=123' + * + * // 数组类型参数 + * generateUrlWithQuery('/api/users', { ids: [1, 2, 3], tags: ['a', 'b'] }) // => '/api/users?ids=1,2,3&tags=a,b' + * + * // 嵌套数组(会被扁平化) + * generateUrlWithQuery('/api/data', { matrix: [[1, 2], [3, 4]] }) // => '/api/data?matrix=1,2,3,4' + * + * // 混合类型数组 + * generateUrlWithQuery('/api/data', { mixed: [1, 'two', true, null, undefined] }) // => '/api/data?mixed=1,two,true,,' + * + * // 复杂类型数组(使用 toString() 转换) + * generateUrlWithQuery('/api/data', { objects: [{ a: 1 }, { b: 2 }] }) // => '/api/data?objects=[object Object],[object Object]' * ``` */ const generateUrlWithQuery = (pathname: string, queryParams: QueryParams = {}): string => { @@ -45,7 +57,8 @@ const generateUrlWithQuery = (pathname: string, queryParams: QueryParams = {}): Object.entries(filteredParams).forEach(([key, value]) => { if (['string', 'number', 'boolean', 'array'].includes(getTypeOfValue(value))) { - params.append(key, String(value)); + const stringValue = String(value); + stringValue && params.append(key, stringValue); } });