query-string 8.2.0 → 9.1.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 +110 -1
  2. package/base.js +37 -18
  3. package/package.json +7 -7
  4. package/readme.md +110 -1
package/base.d.ts CHANGED
@@ -87,7 +87,14 @@ export type ParseOptions = {
87
87
  //=> {foo: ['1', '2', '3']}
88
88
  ```
89
89
  */
90
- readonly arrayFormat?: 'bracket' | 'index' | 'comma' | 'separator' | 'bracket-separator' | 'colon-list-separator' | 'none';
90
+ readonly arrayFormat?:
91
+ | 'bracket'
92
+ | 'index'
93
+ | 'comma'
94
+ | 'separator'
95
+ | 'bracket-separator'
96
+ | 'colon-list-separator'
97
+ | 'none';
91
98
 
92
99
  /**
93
100
  The character used to separate array elements when using `{arrayFormat: 'separator'}`.
@@ -169,6 +176,108 @@ export type ParseOptions = {
169
176
  ```
170
177
  */
171
178
  readonly parseFragmentIdentifier?: boolean;
179
+
180
+ /**
181
+ Specify a pre-defined schema to be used when parsing values. The types specified will take precedence over options such as: `parseNumber`, `parseBooleans`, and `arrayFormat`.
182
+
183
+ Use this feature to override the type of a value. This can be useful when the type is ambiguous such as a phone number (see example 1 and 2).
184
+
185
+ It is possible to provide a custom function as the parameter type. The parameter's value will equal the function's return value (see example 4).
186
+
187
+ NOTE: Array types (`string[]` and `number[]`) will have no effect if `arrayFormat` is set to `none` (see example 5).
188
+
189
+ @default {}
190
+
191
+ @example
192
+ Parse `phoneNumber` as a string, overriding the `parseNumber` option:
193
+ ```
194
+ import queryString from 'query-string';
195
+
196
+ queryString.parse('?phoneNumber=%2B380951234567&id=1', {
197
+ parseNumbers: true,
198
+ types: {
199
+ phoneNumber: 'string',
200
+ }
201
+ });
202
+ //=> {phoneNumber: '+380951234567', id: 1}
203
+ ```
204
+
205
+ @example
206
+ Parse `items` as an array of strings, overriding the `parseNumber` option:
207
+ ```
208
+ import queryString from 'query-string';
209
+
210
+ queryString.parse('?age=20&items=1%2C2%2C3', {
211
+ parseNumber: true,
212
+ types: {
213
+ items: 'string[]',
214
+ }
215
+ });
216
+ //=> {age: 20, items: ['1', '2', '3']}
217
+ ```
218
+
219
+ @example
220
+ Parse `age` as a number, even when `parseNumber` is false:
221
+ ```
222
+ import queryString from 'query-string';
223
+
224
+ queryString.parse('?age=20&id=01234&zipcode=90210', {
225
+ types: {
226
+ age: 'number',
227
+ }
228
+ });
229
+ //=> {age: 20, id: '01234', zipcode: '90210 }
230
+ ```
231
+
232
+ @example
233
+ Parse `age` using a custom value parser:
234
+ ```
235
+ import queryString from 'query-string';
236
+
237
+ queryString.parse('?age=20&id=01234&zipcode=90210', {
238
+ types: {
239
+ age: (value) => value * 2,
240
+ }
241
+ });
242
+ //=> {age: 40, id: '01234', zipcode: '90210 }
243
+ ```
244
+
245
+ @example
246
+ Array types will have no effect when `arrayFormat` is set to `none`
247
+ ```
248
+ queryString.parse('ids=001%2C002%2C003&foods=apple%2Corange%2Cmango', {
249
+ arrayFormat: 'none',
250
+ types: {
251
+ ids: 'number[]',
252
+ foods: 'string[]',
253
+ },
254
+ }
255
+ //=> {ids:'001,002,003', foods:'apple,orange,mango'}
256
+ ```
257
+
258
+ @example
259
+ Parse a query utilizing all types:
260
+ ```
261
+ import queryString from 'query-string';
262
+
263
+ queryString.parse('?ids=001%2C002%2C003&items=1%2C2%2C3&price=22%2E00&numbers=1%2C2%2C3&double=5&number=20', {
264
+ arrayFormat: 'comma',
265
+ types: {
266
+ ids: 'string',
267
+ items: 'string[]',
268
+ price: 'string',
269
+ numbers: 'number[]',
270
+ double: (value) => value * 2,
271
+ number: 'number',
272
+ },
273
+ });
274
+ //=> {ids: '001,002,003', items: ['1', '2', '3'], price: '22.00', numbers: [1, 2, 3], double: 10, number: 20}
275
+ ```
276
+ */
277
+ readonly types?: Record<
278
+ string,
279
+ 'number' | 'string' | 'string[]' | 'number[]' | ((value: string) => unknown)
280
+ >;
172
281
  };
173
282
 
174
283
  // eslint-disable-next-line @typescript-eslint/ban-types
package/base.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import decodeComponent from 'decode-uri-component';
2
- import splitOnFirst from 'split-on-first';
3
2
  import {includeKeys} from 'filter-obj';
3
+ import splitOnFirst from 'split-on-first';
4
4
 
5
5
  const isNullOrUndefined = value => value === null || value === undefined;
6
6
 
7
7
  // eslint-disable-next-line unicorn/prefer-code-point
8
- const strictUriEncode = string => encodeURIComponent(string).replace(/[!'()*]/g, x => `%${x.charCodeAt(0).toString(16).toUpperCase()}`);
8
+ const strictUriEncode = string => encodeURIComponent(string).replaceAll(/[!'()*]/g, x => `%${x.charCodeAt(0).toString(16).toUpperCase()}`);
9
9
 
10
10
  const encodeFragmentIdentifier = Symbol('encodeFragmentIdentifier');
11
11
 
@@ -87,7 +87,7 @@ function encoderForArrayFormat(options) {
87
87
  case 'comma':
88
88
  case 'separator':
89
89
  case 'bracket-separator': {
90
- const keyValueSep = options.arrayFormat === 'bracket-separator'
90
+ const keyValueSeparator = options.arrayFormat === 'bracket-separator'
91
91
  ? '[]='
92
92
  : '=';
93
93
 
@@ -104,7 +104,7 @@ function encoderForArrayFormat(options) {
104
104
  value = value === null ? '' : value;
105
105
 
106
106
  if (result.length === 0) {
107
- return [[encode(key, options), keyValueSep, encode(value, options)].join('')];
107
+ return [[encode(key, options), keyValueSeparator, encode(value, options)].join('')];
108
108
  }
109
109
 
110
110
  return [[result, encode(value, options)].join(options.arrayFormatSeparator)];
@@ -300,11 +300,25 @@ function getHash(url) {
300
300
  return hash;
301
301
  }
302
302
 
303
- function parseValue(value, options) {
303
+ function parseValue(value, options, type) {
304
+ if (type === 'string' && typeof value === 'string') {
305
+ return value;
306
+ }
307
+
308
+ if (typeof type === 'function' && typeof value === 'string') {
309
+ return type(value);
310
+ }
311
+
312
+ if (options.parseBooleans && value !== null && (value.toLowerCase() === 'true' || value.toLowerCase() === 'false')) {
313
+ return value.toLowerCase() === 'true';
314
+ }
315
+
316
+ if (type === 'number' && !Number.isNaN(Number(value)) && (typeof value === 'string' && value.trim() !== '')) {
317
+ return Number(value);
318
+ }
319
+
304
320
  if (options.parseNumbers && !Number.isNaN(Number(value)) && (typeof value === 'string' && value.trim() !== '')) {
305
- value = Number(value);
306
- } else if (options.parseBooleans && value !== null && (value.toLowerCase() === 'true' || value.toLowerCase() === 'false')) {
307
- value = value.toLowerCase() === 'true';
321
+ return Number(value);
308
322
  }
309
323
 
310
324
  return value;
@@ -328,6 +342,7 @@ export function parse(query, options) {
328
342
  arrayFormatSeparator: ',',
329
343
  parseNumbers: false,
330
344
  parseBooleans: false,
345
+ types: Object.create(null),
331
346
  ...options,
332
347
  };
333
348
 
@@ -353,7 +368,7 @@ export function parse(query, options) {
353
368
  continue;
354
369
  }
355
370
 
356
- const parameter_ = options.decode ? parameter.replace(/\+/g, ' ') : parameter;
371
+ const parameter_ = options.decode ? parameter.replaceAll('+', ' ') : parameter;
357
372
 
358
373
  let [key, value] = splitOnFirst(parameter_, '=');
359
374
 
@@ -368,12 +383,15 @@ export function parse(query, options) {
368
383
  }
369
384
 
370
385
  for (const [key, value] of Object.entries(returnValue)) {
371
- if (typeof value === 'object' && value !== null) {
386
+ if (typeof value === 'object' && value !== null && options.types[key] !== 'string') {
372
387
  for (const [key2, value2] of Object.entries(value)) {
373
- value[key2] = parseValue(value2, options);
388
+ const type = options.types[key] ? options.types[key].replace('[]', '') : undefined;
389
+ value[key2] = parseValue(value2, options, type);
374
390
  }
391
+ } else if (typeof value === 'object' && value !== null && options.types[key] === 'string') {
392
+ returnValue[key] = Object.values(value).join(options.arrayFormatSeparator);
375
393
  } else {
376
- returnValue[key] = parseValue(value, options);
394
+ returnValue[key] = parseValue(value, options, options.types[key]);
377
395
  }
378
396
  }
379
397
 
@@ -395,10 +413,13 @@ export function stringify(object, options) {
395
413
  return '';
396
414
  }
397
415
 
398
- options = {encode: true,
416
+ options = {
417
+ encode: true,
399
418
  strict: true,
400
419
  arrayFormat: 'none',
401
- arrayFormatSeparator: ',', ...options};
420
+ arrayFormatSeparator: ',',
421
+ ...options,
422
+ };
402
423
 
403
424
  validateArrayFormatSeparator(options.arrayFormatSeparator);
404
425
 
@@ -484,12 +505,10 @@ export function stringifyUrl(object, options) {
484
505
  };
485
506
 
486
507
  let queryString = stringify(query, options);
487
- if (queryString) {
488
- queryString = `?${queryString}`;
489
- }
508
+ queryString &&= `?${queryString}`;
490
509
 
491
510
  let hash = getHash(object.url);
492
- if (object.fragmentIdentifier) {
511
+ if (typeof object.fragmentIdentifier === 'string') {
493
512
  const urlObjectForFragmentEncode = new URL(url);
494
513
  urlObjectForFragmentEncode.hash = object.fragmentIdentifier;
495
514
  hash = options[encodeFragmentIdentifier] ? urlObjectForFragmentEncode.hash : `#${object.fragmentIdentifier}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "query-string",
3
- "version": "8.2.0",
3
+ "version": "9.1.0",
4
4
  "description": "Parse and stringify URL query strings",
5
5
  "license": "MIT",
6
6
  "repository": "sindresorhus/query-string",
@@ -17,7 +17,7 @@
17
17
  },
18
18
  "sideEffects": false,
19
19
  "engines": {
20
- "node": ">=14.16"
20
+ "node": ">=18"
21
21
  },
22
22
  "scripts": {
23
23
  "benchmark": "node benchmark.js",
@@ -51,12 +51,12 @@
51
51
  "split-on-first": "^3.0.0"
52
52
  },
53
53
  "devDependencies": {
54
- "ava": "^5.1.0",
54
+ "ava": "^6.1.1",
55
55
  "benchmark": "^2.1.4",
56
- "deep-equal": "^2.1.0",
57
- "fast-check": "^3.4.0",
58
- "tsd": "^0.25.0",
59
- "xo": "^0.54.2"
56
+ "deep-equal": "^2.2.3",
57
+ "fast-check": "^3.15.1",
58
+ "tsd": "^0.30.7",
59
+ "xo": "^0.57.0"
60
60
  },
61
61
  "tsd": {
62
62
  "compilerOptions": {
package/readme.md CHANGED
@@ -44,7 +44,8 @@
44
44
  npm install query-string
45
45
  ```
46
46
 
47
- **Not `npm install querystring`!!!!!**
47
+ > [!WARNING]
48
+ > Remember the hyphen! Do not install the deprecated [`querystring`](https://github.com/Gozala/querystring) package!
48
49
 
49
50
  For browser usage, this package targets the latest version of Chrome, Firefox, and Safari.
50
51
 
@@ -223,6 +224,114 @@ queryString.parse('foo=true', {parseBooleans: true});
223
224
 
224
225
  Parse the value as a boolean type instead of string type if it's a boolean.
225
226
 
227
+ ##### types
228
+
229
+ Type: `object`\
230
+ Default: `{}`
231
+
232
+ Specify a pre-defined schema to be used when parsing values. The types specified will take precedence over options such as: `parseNumber`, `parseBooleans`, and `arrayFormat`.
233
+
234
+ Use this feature to override the type of a value. This can be useful when the type is ambiguous such as a phone number.
235
+
236
+ It is possible to provide a custom function as the parameter type. The parameter's value will equal the function's return value.
237
+
238
+ Supported Types:
239
+
240
+ - `'string'`: Parse `phoneNumber` as a string (overriding the `parseNumber` option):
241
+
242
+ ```js
243
+ import queryString from 'query-string';
244
+
245
+ queryString.parse('?phoneNumber=%2B380951234567&id=1', {
246
+ parseNumbers: true,
247
+ types: {
248
+ phoneNumber: 'string',
249
+ }
250
+ });
251
+ //=> {phoneNumber: '+380951234567', id: 1}
252
+ ```
253
+
254
+ - `'number'`: Parse `age` as a number (even when `parseNumber` is false):
255
+
256
+ ```js
257
+ import queryString from 'query-string';
258
+
259
+ queryString.parse('?age=20&id=01234&zipcode=90210', {
260
+ types: {
261
+ age: 'number',
262
+ }
263
+ });
264
+ //=> {age: 20, id: '01234', zipcode: '90210 }
265
+ ```
266
+
267
+ - `'string[]'`: Parse `items` as an array of strings (overriding the `parseNumber` option):
268
+
269
+ ```js
270
+ import queryString from 'query-string';
271
+
272
+ queryString.parse('?age=20&items=1%2C2%2C3', {
273
+ parseNumber: true,
274
+ types: {
275
+ items: 'string[]',
276
+ }
277
+ });
278
+ //=> {age: 20, items: ['1', '2', '3']}
279
+ ```
280
+
281
+ - `'number[]'`: Parse `items` as an array of numbers (even when `parseNumber` is false):
282
+
283
+ ```js
284
+ import queryString from 'query-string';
285
+
286
+ queryString.parse('?age=20&items=1%2C2%2C3', {
287
+ types: {
288
+ items: 'number[]',
289
+ }
290
+ });
291
+ //=> {age: '20', items: [1, 2, 3]}
292
+ ```
293
+
294
+ - `'Function'`: Provide a custom function as the parameter type. The parameter's value will equal the function's return value.
295
+
296
+ ```js
297
+ import queryString from 'query-string';
298
+
299
+ queryString.parse('?age=20&id=01234&zipcode=90210', {
300
+ types: {
301
+ age: (value) => value * 2,
302
+ }
303
+ });
304
+ //=> {age: 40, id: '01234', zipcode: '90210 }
305
+ ```
306
+
307
+ NOTE: Array types (`string[]` and `number[]`) will have no effect if `arrayFormat` is set to `none`.
308
+
309
+ ```js
310
+ queryString.parse('ids=001%2C002%2C003&foods=apple%2Corange%2Cmango', {
311
+ arrayFormat: 'none',
312
+ types: {
313
+ ids: 'number[]',
314
+ foods: 'string[]',
315
+ },
316
+ }
317
+ //=> {ids:'001,002,003', foods:'apple,orange,mango'}
318
+ ```
319
+
320
+ ###### Function
321
+
322
+ ```js
323
+ import queryString from 'query-string';
324
+
325
+ queryString.parse('?age=20&id=01234&zipcode=90210', {
326
+ types: {
327
+ age: (value) => value * 2,
328
+ }
329
+ });
330
+ //=> {age: 40, id: '01234', zipcode: '90210 }
331
+ ```
332
+
333
+ Parse the value as a boolean type instead of string type if it's a boolean.
334
+
226
335
  ### .stringify(object, options?)
227
336
 
228
337
  Stringify an object into a query string and sorting the keys.