query-string 9.0.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 +26 -8
  3. package/package.json +1 -1
  4. package/readme.md +108 -0
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,6 +1,6 @@
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
 
@@ -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
 
@@ -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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "query-string",
3
- "version": "9.0.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",
package/readme.md CHANGED
@@ -224,6 +224,114 @@ queryString.parse('foo=true', {parseBooleans: true});
224
224
 
225
225
  Parse the value as a boolean type instead of string type if it's a boolean.
226
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
+
227
335
  ### .stringify(object, options?)
228
336
 
229
337
  Stringify an object into a query string and sorting the keys.