query-string 9.0.0 → 9.1.1

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 +27 -9
  3. package/package.json +1 -1
  4. package/readme.md +108 -12
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
 
@@ -221,7 +221,7 @@ function parserForArrayFormat(options) {
221
221
 
222
222
  const arrayValue = value === null
223
223
  ? []
224
- : value.split(options.arrayFormatSeparator).map(item => decode(item, options));
224
+ : decode(value, options).split(options.arrayFormatSeparator);
225
225
 
226
226
  if (accumulator[key] === undefined) {
227
227
  accumulator[key] = arrayValue;
@@ -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.1",
4
4
  "description": "Parse and stringify URL query strings",
5
5
  "license": "MIT",
6
6
  "repository": "sindresorhus/query-string",
package/readme.md CHANGED
@@ -19,18 +19,6 @@
19
19
  <a href="https://standardresume.co/tech">
20
20
  <img src="https://sindresorhus.com/assets/thanks/standard-resume-logo.svg" width="180"/>
21
21
  </a>
22
- <br>
23
- <br>
24
- <br>
25
- <a href="https://strapi.io/?ref=sindresorhus">
26
- <div>
27
- <img src="https://sindresorhus.com/assets/thanks/strapi-logo-white-bg.png" width="200" alt="Strapi">
28
- </div>
29
- <b>Strapi is the leading open-source headless CMS.</b>
30
- <div>
31
- <sup>It’s 100% JavaScript, fully customizable, and developer-first.</sup>
32
- </div>
33
- </a>
34
22
  </p>
35
23
  </div>
36
24
 
@@ -224,6 +212,114 @@ queryString.parse('foo=true', {parseBooleans: true});
224
212
 
225
213
  Parse the value as a boolean type instead of string type if it's a boolean.
226
214
 
215
+ ##### types
216
+
217
+ Type: `object`\
218
+ Default: `{}`
219
+
220
+ 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`.
221
+
222
+ Use this feature to override the type of a value. This can be useful when the type is ambiguous such as a phone number.
223
+
224
+ It is possible to provide a custom function as the parameter type. The parameter's value will equal the function's return value.
225
+
226
+ Supported Types:
227
+
228
+ - `'string'`: Parse `phoneNumber` as a string (overriding the `parseNumber` option):
229
+
230
+ ```js
231
+ import queryString from 'query-string';
232
+
233
+ queryString.parse('?phoneNumber=%2B380951234567&id=1', {
234
+ parseNumbers: true,
235
+ types: {
236
+ phoneNumber: 'string',
237
+ }
238
+ });
239
+ //=> {phoneNumber: '+380951234567', id: 1}
240
+ ```
241
+
242
+ - `'number'`: Parse `age` as a number (even when `parseNumber` is false):
243
+
244
+ ```js
245
+ import queryString from 'query-string';
246
+
247
+ queryString.parse('?age=20&id=01234&zipcode=90210', {
248
+ types: {
249
+ age: 'number',
250
+ }
251
+ });
252
+ //=> {age: 20, id: '01234', zipcode: '90210 }
253
+ ```
254
+
255
+ - `'string[]'`: Parse `items` as an array of strings (overriding the `parseNumber` option):
256
+
257
+ ```js
258
+ import queryString from 'query-string';
259
+
260
+ queryString.parse('?age=20&items=1%2C2%2C3', {
261
+ parseNumber: true,
262
+ types: {
263
+ items: 'string[]',
264
+ }
265
+ });
266
+ //=> {age: 20, items: ['1', '2', '3']}
267
+ ```
268
+
269
+ - `'number[]'`: Parse `items` as an array of numbers (even when `parseNumber` is false):
270
+
271
+ ```js
272
+ import queryString from 'query-string';
273
+
274
+ queryString.parse('?age=20&items=1%2C2%2C3', {
275
+ types: {
276
+ items: 'number[]',
277
+ }
278
+ });
279
+ //=> {age: '20', items: [1, 2, 3]}
280
+ ```
281
+
282
+ - `'Function'`: Provide a custom function as the parameter type. The parameter's value will equal the function's return value.
283
+
284
+ ```js
285
+ import queryString from 'query-string';
286
+
287
+ queryString.parse('?age=20&id=01234&zipcode=90210', {
288
+ types: {
289
+ age: (value) => value * 2,
290
+ }
291
+ });
292
+ //=> {age: 40, id: '01234', zipcode: '90210 }
293
+ ```
294
+
295
+ NOTE: Array types (`string[]` and `number[]`) will have no effect if `arrayFormat` is set to `none`.
296
+
297
+ ```js
298
+ queryString.parse('ids=001%2C002%2C003&foods=apple%2Corange%2Cmango', {
299
+ arrayFormat: 'none',
300
+ types: {
301
+ ids: 'number[]',
302
+ foods: 'string[]',
303
+ },
304
+ }
305
+ //=> {ids:'001,002,003', foods:'apple,orange,mango'}
306
+ ```
307
+
308
+ ###### Function
309
+
310
+ ```js
311
+ import queryString from 'query-string';
312
+
313
+ queryString.parse('?age=20&id=01234&zipcode=90210', {
314
+ types: {
315
+ age: (value) => value * 2,
316
+ }
317
+ });
318
+ //=> {age: 40, id: '01234', zipcode: '90210 }
319
+ ```
320
+
321
+ Parse the value as a boolean type instead of string type if it's a boolean.
322
+
227
323
  ### .stringify(object, options?)
228
324
 
229
325
  Stringify an object into a query string and sorting the keys.