Skip to content

Commit 720f2ff

Browse files
committed
Fix custom type functions with array formats
Fixes #401
1 parent b46abfc commit 720f2ff

File tree

4 files changed

+90
-13
lines changed

4 files changed

+90
-13
lines changed

base.d.ts

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -182,14 +182,14 @@ export type ParseOptions = {
182182
183183
Use this option to explicitly define the type of a specific parameter—particularly useful in cases where the type might otherwise be ambiguous (e.g., phone numbers or IDs).
184184
185-
You can also provide a custom function to transform the value. The function will receive the raw string and should return the desired parsed result (see Example 4).
185+
You can also provide a custom function to transform the value. The function will receive the raw string and should return the desired parsed result. When used with array formats (like `comma`, `separator`, `bracket`, etc.), the function is applied to each array element individually.
186186
187187
NOTE: Array types (`string[]`, `number[]`) are ignored if `arrayFormat` is set to `'none'`.
188188
189189
@default {}
190190
191191
@example
192-
Parse `phoneNumber` as a string, overriding the `parseNumber` option:
192+
Parse `phoneNumber` as a string, overriding the `parseNumbers` option:
193193
```
194194
import queryString from 'query-string';
195195
@@ -203,12 +203,12 @@ export type ParseOptions = {
203203
```
204204
205205
@example
206-
Parse `items` as an array of strings, overriding the `parseNumber` option:
206+
Parse `items` as an array of strings, overriding the `parseNumbers` option:
207207
```
208208
import queryString from 'query-string';
209209
210210
queryString.parse('?age=20&items=1%2C2%2C3', {
211-
parseNumber: true,
211+
parseNumbers: true,
212212
types: {
213213
items: 'string[]',
214214
}
@@ -236,12 +236,27 @@ export type ParseOptions = {
236236
237237
queryString.parse('?age=20&id=01234&zipcode=90210', {
238238
types: {
239-
age: (value) => value * 2,
239+
age: value => value * 2,
240240
}
241241
});
242242
//=> {age: 40, id: '01234', zipcode: '90210'}
243243
```
244244
245+
@example
246+
Custom functions are applied to each array element when using array formats:
247+
```
248+
import queryString from 'query-string';
249+
250+
// With arrays, the function is applied to each element
251+
queryString.parse('?scores=10,20,30', {
252+
arrayFormat: 'comma',
253+
types: {
254+
scores: value => Number(value) * 2,
255+
}
256+
});
257+
//=> {scores: [20, 40, 60]}
258+
```
259+
245260
@example
246261
Array types are ignored when `arrayFormat` is set to `'none'`:
247262
```
@@ -267,7 +282,7 @@ export type ParseOptions = {
267282
items: 'string[]',
268283
price: 'string',
269284
numbers: 'number[]',
270-
double: (value) => value * 2,
285+
double: value => value * 2,
271286
number: 'number',
272287
},
273288
});

base.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,8 @@ export function parse(query, options) {
403403
for (const [key, value] of Object.entries(returnValue)) {
404404
if (typeof value === 'object' && value !== null && options.types[key] !== 'string') {
405405
for (const [key2, value2] of Object.entries(value)) {
406-
const type = options.types[key] ? options.types[key].replace('[]', '') : undefined;
406+
const typeOption = options.types[key];
407+
const type = typeof typeOption === 'function' ? typeOption : (typeOption ? typeOption.replace('[]', '') : undefined);
407408
value[key2] = parseValue(value2, options, type);
408409
}
409410
} else if (typeof value === 'object' && value !== null && options.types[key] === 'string') {

readme.md

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -201,13 +201,11 @@ Parse the value as a boolean type instead of string type if it's a boolean.
201201
Type: `object`\
202202
Default: `{}`
203203

204-
205204
Specifies a schema for parsing query values with explicit type declarations. When defined, the types provided here take precedence over general parsing options such as `parseNumbers`, `parseBooleans`, and `arrayFormat`.
206205

207206
Use this option to explicitly define the type of a specific parameter—particularly useful in cases where the type might otherwise be ambiguous (e.g., phone numbers or IDs).
208207

209-
You can also provide a custom function to transform the value. The function will receive the raw string and should return the desired parsed result.
210-
208+
You can also provide a custom function to transform the value. The function will receive the raw string and should return the desired parsed result. When used with array formats (like `comma`, `separator`, `bracket`, etc.), the function is applied to each array element individually.
211209

212210
Supported Types:
213211

@@ -281,17 +279,26 @@ queryString.parse('?age=20&items=1%2C2%2C3', {
281279
//=> {age: '20', items: [1, 2, 3]}
282280
```
283281

284-
- `'Function'`: Provide a custom function as the parameter type. The parameter's value will equal the function's return value.
282+
- `'Function'`: Provide a custom function as the parameter type. The parameter's value will equal the function's return value. When used with array formats (like `comma`, `separator`, `bracket`, etc.), the function is applied to each array element individually.
285283

286284
```js
287285
import queryString from 'query-string';
288286

289287
queryString.parse('?age=20&id=01234&zipcode=90210', {
290288
types: {
291-
age: (value) => value * 2,
289+
age: value => value * 2,
292290
}
293291
});
294292
//=> {age: 40, id: '01234', zipcode: '90210'}
293+
294+
// With arrays, the function is applied to each element
295+
queryString.parse('?scores=10,20,30', {
296+
arrayFormat: 'comma',
297+
types: {
298+
scores: value => Number(value) * 2,
299+
}
300+
});
301+
//=> {scores: [20, 40, 60]}
295302
```
296303

297304
NOTE: Array types (`string[]`, `number[]`) are ignored if `arrayFormat` is set to `'none'`.
@@ -314,7 +321,7 @@ import queryString from 'query-string';
314321

315322
queryString.parse('?age=20&id=01234&zipcode=90210', {
316323
types: {
317-
age: (value) => value * 2,
324+
age: value => value * 2,
318325
}
319326
});
320327
//=> {age: 40, id: '01234', zipcode: '90210'}

test/parse.js

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -625,3 +625,57 @@ test('types option: boolean type accepts an empty string as true', t => {
625625
},
626626
);
627627
});
628+
629+
test('types option: function types with arrays apply to each element', t => {
630+
// Test with comma format
631+
t.deepEqual(queryString.parse('scores=10,20,30', {
632+
arrayFormat: 'comma',
633+
types: {
634+
scores: value => Number(value) * 2,
635+
},
636+
}), {
637+
scores: [20, 40, 60],
638+
});
639+
640+
// Test with bracket format
641+
t.deepEqual(queryString.parse('scores[]=5&scores[]=10&scores[]=15', {
642+
arrayFormat: 'bracket',
643+
types: {
644+
scores: value => Number(value) + 10,
645+
},
646+
}), {
647+
scores: [15, 20, 25],
648+
});
649+
650+
// Test with separator format
651+
t.deepEqual(queryString.parse('scores=1|2|3', {
652+
arrayFormat: 'separator',
653+
arrayFormatSeparator: '|',
654+
types: {
655+
scores: value => `item-${value}`,
656+
},
657+
}), {
658+
scores: ['item-1', 'item-2', 'item-3'],
659+
});
660+
661+
// Test function with single value still works
662+
t.deepEqual(queryString.parse('score=42', {
663+
types: {
664+
score: value => Number(value) / 2,
665+
},
666+
}), {
667+
score: 21,
668+
});
669+
670+
// Test mixed types in same query
671+
t.deepEqual(queryString.parse('nums=1,2,3&single=10', {
672+
arrayFormat: 'comma',
673+
types: {
674+
nums: value => Number(value) * 3,
675+
single: value => Number(value) * 2,
676+
},
677+
}), {
678+
nums: [3, 6, 9],
679+
single: 20,
680+
});
681+
});

0 commit comments

Comments
 (0)