query-string 9.3.0 → 9.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/base.d.ts +21 -6
  2. package/base.js +75 -47
  3. package/package.json +1 -1
  4. package/readme.md +16 -6
package/base.d.ts CHANGED
@@ -182,14 +182,14 @@ export type ParseOptions = {
182
182
 
183
183
  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).
184
184
 
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.
186
186
 
187
187
  NOTE: Array types (`string[]`, `number[]`) are ignored if `arrayFormat` is set to `'none'`.
188
188
 
189
189
  @default {}
190
190
 
191
191
  @example
192
- Parse `phoneNumber` as a string, overriding the `parseNumber` option:
192
+ Parse `phoneNumber` as a string, overriding the `parseNumbers` option:
193
193
  ```
194
194
  import queryString from 'query-string';
195
195
 
@@ -203,12 +203,12 @@ export type ParseOptions = {
203
203
  ```
204
204
 
205
205
  @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:
207
207
  ```
208
208
  import queryString from 'query-string';
209
209
 
210
210
  queryString.parse('?age=20&items=1%2C2%2C3', {
211
- parseNumber: true,
211
+ parseNumbers: true,
212
212
  types: {
213
213
  items: 'string[]',
214
214
  }
@@ -236,12 +236,27 @@ export type ParseOptions = {
236
236
 
237
237
  queryString.parse('?age=20&id=01234&zipcode=90210', {
238
238
  types: {
239
- age: (value) => value * 2,
239
+ age: value => value * 2,
240
240
  }
241
241
  });
242
242
  //=> {age: 40, id: '01234', zipcode: '90210'}
243
243
  ```
244
244
 
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
+
245
260
  @example
246
261
  Array types are ignored when `arrayFormat` is set to `'none'`:
247
262
  ```
@@ -267,7 +282,7 @@ export type ParseOptions = {
267
282
  items: 'string[]',
268
283
  price: 'string',
269
284
  numbers: 'number[]',
270
- double: (value) => value * 2,
285
+ double: value => value * 2,
271
286
  number: 'number',
272
287
  },
273
288
  });
package/base.js CHANGED
@@ -24,15 +24,12 @@ function encoderForArrayFormat(options) {
24
24
  }
25
25
 
26
26
  if (value === null) {
27
- return [
28
- ...result, [encode(key, options), '[', index, ']'].join(''),
29
- ];
27
+ result.push([encode(key, options), '[', index, ']'].join(''));
28
+ return result;
30
29
  }
31
30
 
32
- return [
33
- ...result,
34
- [encode(key, options), '[', encode(index, options), ']=', encode(value, options)].join(''),
35
- ];
31
+ result.push([encode(key, options), '[', encode(index, options), ']=', encode(value, options)].join(''));
32
+ return result;
36
33
  };
37
34
  }
38
35
 
@@ -47,16 +44,12 @@ function encoderForArrayFormat(options) {
47
44
  }
48
45
 
49
46
  if (value === null) {
50
- return [
51
- ...result,
52
- [encode(key, options), '[]'].join(''),
53
- ];
47
+ result.push([encode(key, options), '[]'].join(''));
48
+ return result;
54
49
  }
55
50
 
56
- return [
57
- ...result,
58
- [encode(key, options), '[]=', encode(value, options)].join(''),
59
- ];
51
+ result.push([encode(key, options), '[]=', encode(value, options)].join(''));
52
+ return result;
60
53
  };
61
54
  }
62
55
 
@@ -71,16 +64,12 @@ function encoderForArrayFormat(options) {
71
64
  }
72
65
 
73
66
  if (value === null) {
74
- return [
75
- ...result,
76
- [encode(key, options), ':list='].join(''),
77
- ];
67
+ result.push([encode(key, options), ':list='].join(''));
68
+ return result;
78
69
  }
79
70
 
80
- return [
81
- ...result,
82
- [encode(key, options), ':list=', encode(value, options)].join(''),
83
- ];
71
+ result.push([encode(key, options), ':list=', encode(value, options)].join(''));
72
+ return result;
84
73
  };
85
74
  }
86
75
 
@@ -104,10 +93,12 @@ function encoderForArrayFormat(options) {
104
93
  value = value === null ? '' : value;
105
94
 
106
95
  if (result.length === 0) {
107
- return [[encode(key, options), keyValueSeparator, encode(value, options)].join('')];
96
+ result.push([encode(key, options), keyValueSeparator, encode(value, options)].join(''));
97
+ return result;
108
98
  }
109
99
 
110
- return [[result, encode(value, options)].join(options.arrayFormatSeparator)];
100
+ result.push(encode(value, options));
101
+ return result;
111
102
  };
112
103
  }
113
104
 
@@ -122,16 +113,12 @@ function encoderForArrayFormat(options) {
122
113
  }
123
114
 
124
115
  if (value === null) {
125
- return [
126
- ...result,
127
- encode(key, options),
128
- ];
116
+ result.push(encode(key, options));
117
+ return result;
129
118
  }
130
119
 
131
- return [
132
- ...result,
133
- [encode(key, options), '=', encode(value, options)].join(''),
134
- ];
120
+ result.push([encode(key, options), '=', encode(value, options)].join(''));
121
+ return result;
135
122
  };
136
123
  }
137
124
  }
@@ -175,7 +162,12 @@ function parserForArrayFormat(options) {
175
162
  return;
176
163
  }
177
164
 
178
- accumulator[key] = [...accumulator[key], value];
165
+ if (!Array.isArray(accumulator[key])) {
166
+ accumulator[key] = [accumulator[key], value];
167
+ return;
168
+ }
169
+
170
+ accumulator[key].push(value);
179
171
  };
180
172
  }
181
173
 
@@ -194,7 +186,12 @@ function parserForArrayFormat(options) {
194
186
  return;
195
187
  }
196
188
 
197
- accumulator[key] = [...accumulator[key], value];
189
+ if (!Array.isArray(accumulator[key])) {
190
+ accumulator[key] = [accumulator[key], value];
191
+ return;
192
+ }
193
+
194
+ accumulator[key].push(value);
198
195
  };
199
196
  }
200
197
 
@@ -226,7 +223,13 @@ function parserForArrayFormat(options) {
226
223
  return;
227
224
  }
228
225
 
229
- accumulator[key] = [...accumulator[key], ...arrayValue];
226
+ if (!Array.isArray(accumulator[key])) {
227
+ accumulator[key] = [accumulator[key]];
228
+ }
229
+
230
+ for (const item of arrayValue) {
231
+ accumulator[key].push(item);
232
+ }
230
233
  };
231
234
  }
232
235
 
@@ -237,7 +240,12 @@ function parserForArrayFormat(options) {
237
240
  return;
238
241
  }
239
242
 
240
- accumulator[key] = [...[accumulator[key]].flat(), value];
243
+ if (Array.isArray(accumulator[key])) {
244
+ accumulator[key].push(value);
245
+ return;
246
+ }
247
+
248
+ accumulator[key] = [accumulator[key], value];
241
249
  };
242
250
  }
243
251
  }
@@ -298,6 +306,12 @@ function getHash(url) {
298
306
  return hash;
299
307
  }
300
308
 
309
+ function getUrlWithoutQuery(url) {
310
+ // Avoid `split('?')` so query-heavy URLs don't allocate large arrays.
311
+ const queryStart = url.indexOf('?');
312
+ return queryStart === -1 ? url : url.slice(0, queryStart);
313
+ }
314
+
301
315
  function parseValue(value, options, type) {
302
316
  if (type === 'string' && typeof value === 'string') {
303
317
  return value;
@@ -381,11 +395,21 @@ export function parse(query, options) {
381
395
  return returnValue;
382
396
  }
383
397
 
384
- for (const parameter of query.split('&')) {
385
- if (parameter === '') {
398
+ // Avoid `split('&')` so separator-heavy inputs don't allocate large arrays of empty strings.
399
+ let parameterStart = 0;
400
+
401
+ for (let index = 0; index <= query.length; index++) {
402
+ if (index < query.length && query[index] !== '&') {
403
+ continue;
404
+ }
405
+
406
+ if (index === parameterStart) {
407
+ parameterStart = index + 1;
386
408
  continue;
387
409
  }
388
410
 
411
+ const parameter = query.slice(parameterStart, index);
412
+ parameterStart = index + 1;
389
413
  const parameter_ = options.decode ? parameter.replaceAll('+', ' ') : parameter;
390
414
 
391
415
  let [key, value] = splitOnFirst(parameter_, '=');
@@ -403,7 +427,8 @@ export function parse(query, options) {
403
427
  for (const [key, value] of Object.entries(returnValue)) {
404
428
  if (typeof value === 'object' && value !== null && options.types[key] !== 'string') {
405
429
  for (const [key2, value2] of Object.entries(value)) {
406
- const type = options.types[key] ? options.types[key].replace('[]', '') : undefined;
430
+ const typeOption = options.types[key];
431
+ const type = typeof typeOption === 'function' ? typeOption : (typeOption ? typeOption.replace('[]', '') : undefined);
407
432
  value[key2] = parseValue(value2, options, type);
408
433
  }
409
434
  } else if (typeof value === 'object' && value !== null && options.types[key] === 'string') {
@@ -497,9 +522,9 @@ export function stringify(object, options) {
497
522
  ).filter(item => item !== undefined);
498
523
  }
499
524
 
500
- return processedArray
501
- .reduce(formatter(key), [])
502
- .join('&');
525
+ const result = processedArray.reduce(formatter(key), []);
526
+ const arrayFormatSeparator = ['comma', 'separator', 'bracket-separator'].includes(options.arrayFormat) ? options.arrayFormatSeparator : '&';
527
+ return result.join(arrayFormatSeparator);
503
528
  }
504
529
 
505
530
  return encode(key, options) + '=' + encode(value, options);
@@ -519,7 +544,7 @@ export function parseUrl(url, options) {
519
544
  }
520
545
 
521
546
  return {
522
- url: url_?.split('?')?.[0] ?? '',
547
+ url: getUrlWithoutQuery(url_ ?? ''),
523
548
  query: parse(extract(url), options),
524
549
  ...(options && options.parseFragmentIdentifier && hash ? {fragmentIdentifier: decode(hash, options)} : {}),
525
550
  };
@@ -533,7 +558,7 @@ export function stringifyUrl(object, options) {
533
558
  ...options,
534
559
  };
535
560
 
536
- const url = removeHash(object.url).split('?')[0] || '';
561
+ const url = getUrlWithoutQuery(removeHash(object.url)) || '';
537
562
  const queryFromUrl = extract(object.url);
538
563
 
539
564
  const query = {
@@ -571,7 +596,10 @@ export function pick(input, filter, options) {
571
596
  }
572
597
 
573
598
  export function exclude(input, filter, options) {
574
- const exclusionFilter = Array.isArray(filter) ? key => !filter.includes(key) : (key, value) => !filter(key, value);
599
+ if (Array.isArray(filter)) {
600
+ const filterSet = new Set(filter);
601
+ return pick(input, key => !filterSet.has(key), options);
602
+ }
575
603
 
576
- return pick(input, exclusionFilter, options);
604
+ return pick(input, (key, value) => !filter(key, value), options);
577
605
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "query-string",
3
- "version": "9.3.0",
3
+ "version": "9.4.0",
4
4
  "description": "Parse and stringify URL query strings",
5
5
  "license": "MIT",
6
6
  "repository": "sindresorhus/query-string",
package/readme.md CHANGED
@@ -13,6 +13,9 @@ npm install query-string
13
13
 
14
14
  For browser usage, this package targets the latest version of Chrome, Firefox, and Safari.
15
15
 
16
+ > [!TIP]
17
+ > Consider using [`URLSearchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) for simple use cases. It's a native browser API that handles basic query string operations.
18
+
16
19
  ## Usage
17
20
 
18
21
  ```js
@@ -201,13 +204,11 @@ Parse the value as a boolean type instead of string type if it's a boolean.
201
204
  Type: `object`\
202
205
  Default: `{}`
203
206
 
204
-
205
207
  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`.
206
208
 
207
209
  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).
208
210
 
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
-
211
+ 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.
211
212
 
212
213
  Supported Types:
213
214
 
@@ -281,17 +282,26 @@ queryString.parse('?age=20&items=1%2C2%2C3', {
281
282
  //=> {age: '20', items: [1, 2, 3]}
282
283
  ```
283
284
 
284
- - `'Function'`: Provide a custom function as the parameter type. The parameter's value will equal the function's return value.
285
+ - `'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.
285
286
 
286
287
  ```js
287
288
  import queryString from 'query-string';
288
289
 
289
290
  queryString.parse('?age=20&id=01234&zipcode=90210', {
290
291
  types: {
291
- age: (value) => value * 2,
292
+ age: value => value * 2,
292
293
  }
293
294
  });
294
295
  //=> {age: 40, id: '01234', zipcode: '90210'}
296
+
297
+ // With arrays, the function is applied to each element
298
+ queryString.parse('?scores=10,20,30', {
299
+ arrayFormat: 'comma',
300
+ types: {
301
+ scores: value => Number(value) * 2,
302
+ }
303
+ });
304
+ //=> {scores: [20, 40, 60]}
295
305
  ```
296
306
 
297
307
  NOTE: Array types (`string[]`, `number[]`) are ignored if `arrayFormat` is set to `'none'`.
@@ -314,7 +324,7 @@ import queryString from 'query-string';
314
324
 
315
325
  queryString.parse('?age=20&id=01234&zipcode=90210', {
316
326
  types: {
317
- age: (value) => value * 2,
327
+ age: value => value * 2,
318
328
  }
319
329
  });
320
330
  //=> {age: 40, id: '01234', zipcode: '90210'}