From 419fc0e7c4905022bd83aa7b21b1ce19476a09ed Mon Sep 17 00:00:00 2001 From: ScottEnock Date: Sun, 26 May 2024 12:31:39 +0100 Subject: [PATCH 01/11] feat: implement types schema option parameter --- base.d.ts | 29 ++++++++++++++++ base.js | 22 +++++++++--- test/parse.js | 93 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 139 insertions(+), 5 deletions(-) diff --git a/base.d.ts b/base.d.ts index 4a2a3b0..5e83aed 100644 --- a/base.d.ts +++ b/base.d.ts @@ -1,3 +1,5 @@ +export type CustomValueParser = (value: string) => unknown; + export type ParseOptions = { /** Decode the keys and values. URI components are decoded with [`decode-uri-component`](https://github.com/SamVerschueren/decode-uri-component). @@ -169,6 +171,33 @@ export type ParseOptions = { ``` */ readonly parseFragmentIdentifier?: boolean; + + /** + Specify a pre-defined schema to be used when parsing values. + Use this feature to override configuration options: parseNumber, parseBooleans, and arrayFormat. + pre-defined types specified here will be used even if parsing options such parseNumber as are not enabled. + + @default {} + + @example + ``` + import queryString from 'query-string'; + + queryString.parse("ids=001%2C002%2C003&items=1%2C2%2C3&price=22%2E00&nums=1%2C2%2C3&double=5&number=20", + { arrayFormat: "comma", types: { + ids: "string", + items: "string[]", + price: "string", + nums: "number[]", + double: (value) => value * 2, + number: "number", + }, + }); + //=> {ids: '001,002,003', items: ['1', '2', '3'], price: '22.00', nums: [1, 2, 3], double: 10, number: 20,} + + ``` + */ + readonly types?: Record; }; // eslint-disable-next-line @typescript-eslint/ban-types diff --git a/base.js b/base.js index dc3f742..d9b7bf7 100644 --- a/base.js +++ b/base.js @@ -300,8 +300,16 @@ function getHash(url) { return hash; } -function parseValue(value, options) { - if (options.parseNumbers && !Number.isNaN(Number(value)) && (typeof value === 'string' && value.trim() !== '')) { +function parseValue(value, options, type) { + if (type === 'string' && (typeof value === 'string')) { + return value; + } + + if (typeof type === 'function' && (typeof value === 'string')) { + value = type(value); + } else if (type === 'number' && !Number.isNaN(Number(value)) && (typeof value === 'string' && value.trim() !== '')) { + value = Number(value); + } else if (options.parseNumbers && !Number.isNaN(Number(value)) && (typeof value === 'string' && value.trim() !== '')) { value = Number(value); } else if (options.parseBooleans && value !== null && (value.toLowerCase() === 'true' || value.toLowerCase() === 'false')) { value = value.toLowerCase() === 'true'; @@ -328,6 +336,7 @@ export function parse(query, options) { arrayFormatSeparator: ',', parseNumbers: false, parseBooleans: false, + types: Object.create(null), ...options, }; @@ -368,12 +377,15 @@ export function parse(query, options) { } for (const [key, value] of Object.entries(returnValue)) { - if (typeof value === 'object' && value !== null) { + if (typeof value === 'object' && value !== null && options.types[key] !== 'string') { for (const [key2, value2] of Object.entries(value)) { - value[key2] = parseValue(value2, options); + const type = options.types[key] ? options.types[key].replace('[]', '') : undefined; + value[key2] = parseValue(value2, options, type); } + } else if (typeof value === 'object' && value !== null && options.types[key] === 'string') { + returnValue[key] = Object.values(value).join(options.arrayFormatSeparator); } else { - returnValue[key] = parseValue(value, options); + returnValue[key] = parseValue(value, options, options.types[key]); } } diff --git a/test/parse.js b/test/parse.js index 17de629..70d26e1 100644 --- a/test/parse.js +++ b/test/parse.js @@ -404,3 +404,96 @@ test('query strings having (:list) colon-list-separator arrays', t => { test('query strings having (:list) colon-list-separator arrays including null values', t => { t.deepEqual(queryString.parse('bar:list=one&bar:list=two&foo', {arrayFormat: 'colon-list-separator'}), {bar: ['one', 'two'], foo: null}); }); + +test('pre-defined types parsing: can override a parsed number to be a string ', t => { + t.deepEqual(queryString.parse('phoneNumber=%2B380951234567', { + parseNumbers: true, + types: { + phoneNumber: 'string', + }, + }), {phoneNumber: '+380951234567'}); +}); + +test('pre-defined types parsing: can override parsed numbers arrays to be string[]', t => { + t.deepEqual(queryString.parse('ids=999%2C998%2C997&items=1%2C2%2C3', { + arrayFormat: 'comma', parseNumbers: true, types: { + ids: 'string[]', + }, + }), { + ids: ['999', '998', '997'], + items: [1, 2, 3], + }); +}); + +test('pre-defined types parsing: can override string arrays to be number[]', t => { + t.deepEqual(queryString.parse('ids=001%2C002%2C003&items=1%2C2%2C3', { + arrayFormat: 'comma', types: { + ids: 'number[]', + }, + }), { + ids: [1, 2, 3], + items: ['1', '2', '3'], + }); +}); + +test('pre-defined types parsing: can override an array to be string', t => { + t.deepEqual(queryString.parse('ids=001%2C002%2C003&items=1%2C2%2C3', { + arrayFormat: 'comma', parseNumbers: true, types: { + ids: 'string', + }, + }), { + ids: '001,002,003', + items: [1, 2, 3], + }); +}); + +test('pre-defined types parsing: can override a separator array to be string ', t => { + t.deepEqual(queryString.parse('ids=001|002|003&items=1|2|3', { + arrayFormat: 'separator', arrayFormatSeparator: '|', parseNumbers: true, types: { + ids: 'string', + }, + }), { + ids: '001|002|003', + items: [1, 2, 3], + }); +}); + +test('pre-defined types parsing: when value is not of specified type, it will safely parse the value as string', t => { + t.deepEqual(queryString.parse('id=example', { + arrayFormat: 'comma', types: { + id: 'number', + }, + }), { + id: 'example', + }); +}); + +test('pre-defined types parsing: will parse the value as number if specified in type but parseNumbers is false', t => { + t.deepEqual(queryString.parse('id=123', { + arrayFormat: 'comma', types: { + id: 'number', + }, + }), { + id: 123, + }); +}); + +test('pre-defined types parsing: all supported types work in conjunction with one another', t => { + t.deepEqual(queryString.parse('ids=001%2C002%2C003&items=1%2C2%2C3&price=22%2E00&nums=1%2C2%2C3&double=5&number=20', { + arrayFormat: 'comma', types: { + ids: 'string', + items: 'string[]', + price: 'string', + nums: 'number[]', + double: value => value * 2, + number: 'number', + }, + }), { + ids: '001,002,003', + items: ['1', '2', '3'], + price: '22.00', + nums: [1, 2, 3], + double: 10, + number: 20, + }); +}); From b678af900e6999b2599eb2a217b54c19f93b43f0 Mon Sep 17 00:00:00 2001 From: ScottEnock Date: Thu, 6 Jun 2024 00:27:03 +0100 Subject: [PATCH 02/11] feat: adjust unit test styling --- test/parse.js | 51 ++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 36 insertions(+), 15 deletions(-) diff --git a/test/parse.js b/test/parse.js index 70d26e1..effa74f 100644 --- a/test/parse.js +++ b/test/parse.js @@ -405,7 +405,7 @@ test('query strings having (:list) colon-list-separator arrays including null va t.deepEqual(queryString.parse('bar:list=one&bar:list=two&foo', {arrayFormat: 'colon-list-separator'}), {bar: ['one', 'two'], foo: null}); }); -test('pre-defined types parsing: can override a parsed number to be a string ', t => { +test('types option: can override a parsed number to be a string ', t => { t.deepEqual(queryString.parse('phoneNumber=%2B380951234567', { parseNumbers: true, types: { @@ -414,9 +414,22 @@ test('pre-defined types parsing: can override a parsed number to be a string ', }), {phoneNumber: '+380951234567'}); }); -test('pre-defined types parsing: can override parsed numbers arrays to be string[]', t => { +test('types option: can override a parsed boolean value to be a string', t => { + t.deepEqual(queryString.parse('question=true', { + parseBooleans: true, + types: { + question: 'string', + }, + }), { + question: 'true', + }); +}); + +test('types option: can override parsed numbers arrays to be string[]', t => { t.deepEqual(queryString.parse('ids=999%2C998%2C997&items=1%2C2%2C3', { - arrayFormat: 'comma', parseNumbers: true, types: { + arrayFormat: 'comma', + parseNumbers: true, + types: { ids: 'string[]', }, }), { @@ -425,9 +438,10 @@ test('pre-defined types parsing: can override parsed numbers arrays to be string }); }); -test('pre-defined types parsing: can override string arrays to be number[]', t => { +test('types option: can override string arrays to be number[]', t => { t.deepEqual(queryString.parse('ids=001%2C002%2C003&items=1%2C2%2C3', { - arrayFormat: 'comma', types: { + arrayFormat: 'comma', + types: { ids: 'number[]', }, }), { @@ -436,9 +450,11 @@ test('pre-defined types parsing: can override string arrays to be number[]', t = }); }); -test('pre-defined types parsing: can override an array to be string', t => { +test('types option: can override an array to be string', t => { t.deepEqual(queryString.parse('ids=001%2C002%2C003&items=1%2C2%2C3', { - arrayFormat: 'comma', parseNumbers: true, types: { + arrayFormat: 'comma', + parseNumbers: true, + types: { ids: 'string', }, }), { @@ -447,9 +463,12 @@ test('pre-defined types parsing: can override an array to be string', t => { }); }); -test('pre-defined types parsing: can override a separator array to be string ', t => { +test('types option: can override a separator array to be string ', t => { t.deepEqual(queryString.parse('ids=001|002|003&items=1|2|3', { - arrayFormat: 'separator', arrayFormatSeparator: '|', parseNumbers: true, types: { + arrayFormat: 'separator', + arrayFormatSeparator: '|', + parseNumbers: true, + types: { ids: 'string', }, }), { @@ -458,9 +477,9 @@ test('pre-defined types parsing: can override a separator array to be string ', }); }); -test('pre-defined types parsing: when value is not of specified type, it will safely parse the value as string', t => { +test('types option: when value is not of specified type, it will safely parse the value as string', t => { t.deepEqual(queryString.parse('id=example', { - arrayFormat: 'comma', types: { + types: { id: 'number', }, }), { @@ -468,9 +487,10 @@ test('pre-defined types parsing: when value is not of specified type, it will sa }); }); -test('pre-defined types parsing: will parse the value as number if specified in type but parseNumbers is false', t => { +test('types option: will parse the value as number if specified in type but parseNumbers is false', t => { t.deepEqual(queryString.parse('id=123', { - arrayFormat: 'comma', types: { + arrayFormat: 'comma', + types: { id: 'number', }, }), { @@ -478,9 +498,10 @@ test('pre-defined types parsing: will parse the value as number if specified in }); }); -test('pre-defined types parsing: all supported types work in conjunction with one another', t => { +test('types option: all supported types work in conjunction with one another', t => { t.deepEqual(queryString.parse('ids=001%2C002%2C003&items=1%2C2%2C3&price=22%2E00&nums=1%2C2%2C3&double=5&number=20', { - arrayFormat: 'comma', types: { + arrayFormat: 'comma', + types: { ids: 'string', items: 'string[]', price: 'string', From dc07897d1ad8bd61bba391cfc9e41ecb1311d35a Mon Sep 17 00:00:00 2001 From: ScottEnock Date: Thu, 6 Jun 2024 00:33:36 +0100 Subject: [PATCH 03/11] feat: adjust doc comments + additional examples --- base.d.ts | 89 +++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 73 insertions(+), 16 deletions(-) diff --git a/base.d.ts b/base.d.ts index 5e83aed..0932883 100644 --- a/base.d.ts +++ b/base.d.ts @@ -1,5 +1,3 @@ -export type CustomValueParser = (value: string) => unknown; - export type ParseOptions = { /** Decode the keys and values. URI components are decoded with [`decode-uri-component`](https://github.com/SamVerschueren/decode-uri-component). @@ -173,31 +171,90 @@ export type ParseOptions = { readonly parseFragmentIdentifier?: boolean; /** - Specify a pre-defined schema to be used when parsing values. - Use this feature to override configuration options: parseNumber, parseBooleans, and arrayFormat. - pre-defined types specified here will be used even if parsing options such parseNumber as are not enabled. + Specify a pre-defined schema to be used when parsing values. The types specified will take precedence over global parameters such as: `parseNumber`, `parseBooleans`, and `arrayFormat`. + + Use this feature to override the type for a value. This can be useful when the type is ambiguous such as a phone number (see example 1 and 2). + + Types specified here will be used even when global parsing options such as `parseNumber`, and `arrayFormat` are not enabled (see example 3). + + NOTE: array types (`string[]` and `number[]`) will not work if `arrayFormat` is set to `none`. @default {} @example + Parse `phoneNumber` as a string, overriding the `parseNumber` option: ``` import queryString from 'query-string'; - queryString.parse("ids=001%2C002%2C003&items=1%2C2%2C3&price=22%2E00&nums=1%2C2%2C3&double=5&number=20", - { arrayFormat: "comma", types: { - ids: "string", - items: "string[]", - price: "string", - nums: "number[]", - double: (value) => value * 2, - number: "number", - }, + queryString.parse('?phoneNumber=%2B380951234567&id=1', { + parseNumbers: true, + types: { + phoneNumber: 'string', + } }); - //=> {ids: '001,002,003', items: ['1', '2', '3'], price: '22.00', nums: [1, 2, 3], double: 10, number: 20,} + //=> {phoneNumber: '+380951234567', id: 1} + ``` + + @example + Parse `items` as an array of strings, overriding the `parseNumber` option: + ``` + import queryString from 'query-string'; + + queryString.parse('?age=20&items=1%2C2%2C3', { + parseNumber: true, + types: { + items: 'string[]', + } + }); + //=> {age: 20, items: ['1', '2', '3']} + ``` + + @example + Parse `age` as a number, even when `parseNumber` is false: + ``` + import queryString from 'query-string'; + queryString.parse('?age=20&id=01234&zipcode=90210', { + types: { + age: 'number', + } + }); + //=> {age: 20, id: '01234', zipcode: '90210 } + ``` + + @example + Parse `age` using a custom value parser: + ``` + import queryString from 'query-string'; + + queryString.parse('?age=20&id=01234&zipcode=90210', { + types: { + age: (value) => value * 2, + } + }); + //=> {age: 40, id: '01234', zipcode: '90210 } + ``` + + @example + Parse a query utilizing all types: + ``` + import queryString from 'query-string'; + + queryString.parse("ids=001%2C002%2C003&items=1%2C2%2C3&price=22%2E00&nums=1%2C2%2C3&double=5&number=20", { + arrayFormat: "comma", + types: { + ids: "string", + items: "string[]", + price: "string", + nums: "number[]", + double: (value) => value * 2, + number: "number", + }, + }); + //=> {ids: '001,002,003', items: ['1', '2', '3'], price: '22.00', nums: [1, 2, 3], double: 10, number: 20} ``` */ - readonly types?: Record; + readonly types?: Record unknown)>; }; // eslint-disable-next-line @typescript-eslint/ban-types From 7171f1442fb689328c1ccee81a272eb574f323c6 Mon Sep 17 00:00:00 2001 From: ScottEnock Date: Mon, 8 Jul 2024 14:01:27 +0100 Subject: [PATCH 04/11] refactor: rename nums to numbers --- base.d.ts | 6 +++--- test/parse.js | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/base.d.ts b/base.d.ts index 0932883..2fb61f0 100644 --- a/base.d.ts +++ b/base.d.ts @@ -240,18 +240,18 @@ export type ParseOptions = { ``` import queryString from 'query-string'; - queryString.parse("ids=001%2C002%2C003&items=1%2C2%2C3&price=22%2E00&nums=1%2C2%2C3&double=5&number=20", { + queryString.parse("ids=001%2C002%2C003&items=1%2C2%2C3&price=22%2E00&numbers=1%2C2%2C3&double=5&number=20", { arrayFormat: "comma", types: { ids: "string", items: "string[]", price: "string", - nums: "number[]", + numbers: "number[]", double: (value) => value * 2, number: "number", }, }); - //=> {ids: '001,002,003', items: ['1', '2', '3'], price: '22.00', nums: [1, 2, 3], double: 10, number: 20} + //=> {ids: '001,002,003', items: ['1', '2', '3'], price: '22.00', numbers: [1, 2, 3], double: 10, number: 20} ``` */ readonly types?: Record unknown)>; diff --git a/test/parse.js b/test/parse.js index effa74f..3aa1ef6 100644 --- a/test/parse.js +++ b/test/parse.js @@ -499,13 +499,13 @@ test('types option: will parse the value as number if specified in type but pars }); test('types option: all supported types work in conjunction with one another', t => { - t.deepEqual(queryString.parse('ids=001%2C002%2C003&items=1%2C2%2C3&price=22%2E00&nums=1%2C2%2C3&double=5&number=20', { + t.deepEqual(queryString.parse('ids=001%2C002%2C003&items=1%2C2%2C3&price=22%2E00&numbers=1%2C2%2C3&double=5&number=20', { arrayFormat: 'comma', types: { ids: 'string', items: 'string[]', price: 'string', - nums: 'number[]', + numbers: 'number[]', double: value => value * 2, number: 'number', }, @@ -513,7 +513,7 @@ test('types option: all supported types work in conjunction with one another', t ids: '001,002,003', items: ['1', '2', '3'], price: '22.00', - nums: [1, 2, 3], + numbers: [1, 2, 3], double: 10, number: 20, }); From 00dce5c0ebe35f3070684b2370bbcaed82256deb Mon Sep 17 00:00:00 2001 From: ScottEnock Date: Mon, 8 Jul 2024 14:08:13 +0100 Subject: [PATCH 05/11] refactor: replace double quotes with single quotes --- base.d.ts | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/base.d.ts b/base.d.ts index 2fb61f0..953c940 100644 --- a/base.d.ts +++ b/base.d.ts @@ -171,12 +171,10 @@ export type ParseOptions = { readonly parseFragmentIdentifier?: boolean; /** - Specify a pre-defined schema to be used when parsing values. The types specified will take precedence over global parameters such as: `parseNumber`, `parseBooleans`, and `arrayFormat`. + 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`. Use this feature to override the type for a value. This can be useful when the type is ambiguous such as a phone number (see example 1 and 2). - Types specified here will be used even when global parsing options such as `parseNumber`, and `arrayFormat` are not enabled (see example 3). - NOTE: array types (`string[]` and `number[]`) will not work if `arrayFormat` is set to `none`. @default {} @@ -240,15 +238,15 @@ export type ParseOptions = { ``` import queryString from 'query-string'; - queryString.parse("ids=001%2C002%2C003&items=1%2C2%2C3&price=22%2E00&numbers=1%2C2%2C3&double=5&number=20", { - arrayFormat: "comma", + queryString.parse('ids=001%2C002%2C003&items=1%2C2%2C3&price=22%2E00&numbers=1%2C2%2C3&double=5&number=20', { + arrayFormat: 'comma', types: { - ids: "string", - items: "string[]", - price: "string", - numbers: "number[]", + ids: 'string', + items: 'string[]', + price: 'string', + numbers: 'number[]', double: (value) => value * 2, - number: "number", + number: 'number', }, }); //=> {ids: '001,002,003', items: ['1', '2', '3'], price: '22.00', numbers: [1, 2, 3], double: 10, number: 20} From b890453713863aae635eb2ffbba663f8a1be184e Mon Sep 17 00:00:00 2001 From: ScottEnock Date: Mon, 8 Jul 2024 14:23:24 +0100 Subject: [PATCH 06/11] refactor: adjust return statement + reorder checks within parseValue --- base.js | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/base.js b/base.js index d9b7bf7..ccbdb6e 100644 --- a/base.js +++ b/base.js @@ -1,6 +1,6 @@ import decodeComponent from 'decode-uri-component'; -import splitOnFirst from 'split-on-first'; import {includeKeys} from 'filter-obj'; +import splitOnFirst from 'split-on-first'; const isNullOrUndefined = value => value === null || value === undefined; @@ -301,18 +301,24 @@ function getHash(url) { } function parseValue(value, options, type) { - if (type === 'string' && (typeof value === 'string')) { + if (type === 'string' && typeof value === 'string') { return value; } - if (typeof type === 'function' && (typeof value === 'string')) { - value = type(value); - } else if (type === 'number' && !Number.isNaN(Number(value)) && (typeof value === 'string' && value.trim() !== '')) { - value = Number(value); - } else if (options.parseNumbers && !Number.isNaN(Number(value)) && (typeof value === 'string' && value.trim() !== '')) { - value = Number(value); - } else if (options.parseBooleans && value !== null && (value.toLowerCase() === 'true' || value.toLowerCase() === 'false')) { - value = value.toLowerCase() === 'true'; + if (typeof type === 'function' && typeof value === 'string') { + return type(value); + } + + if (options.parseBooleans && value !== null && (value.toLowerCase() === 'true' || value.toLowerCase() === 'false')) { + return value.toLowerCase() === 'true'; + } + + if (type === 'number' && !Number.isNaN(Number(value)) && (typeof value === 'string' && value.trim() !== '')) { + return Number(value); + } + + if (options.parseNumbers && !Number.isNaN(Number(value)) && (typeof value === 'string' && value.trim() !== '')) { + return Number(value); } return value; From dab36a6821547c7815dcd49831db6cd6ae438aac Mon Sep 17 00:00:00 2001 From: ScottEnock Date: Mon, 8 Jul 2024 15:01:10 +0100 Subject: [PATCH 07/11] feat: implement unit test for when arrayFormat is none --- test/parse.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/parse.js b/test/parse.js index 3aa1ef6..3409255 100644 --- a/test/parse.js +++ b/test/parse.js @@ -487,6 +487,19 @@ test('types option: when value is not of specified type, it will safely parse th }); }); +test('types option: array types will have no effect if arrayFormat is set to "none"', t => { + t.deepEqual(queryString.parse('ids=001%2C002%2C003&foods=apple%2Corange%2Cmango', { + arrayFormat: 'none', + types: { + ids: 'number[]', + foods: 'string[]', + }, + }), { + ids: '001,002,003', + foods: 'apple,orange,mango', + }); +}); + test('types option: will parse the value as number if specified in type but parseNumbers is false', t => { t.deepEqual(queryString.parse('id=123', { arrayFormat: 'comma', From 965d44118a8db027ea5f99d612698b8aa6357670 Mon Sep 17 00:00:00 2001 From: ScottEnock Date: Mon, 8 Jul 2024 15:02:34 +0100 Subject: [PATCH 08/11] docs: add additional example + documentation message --- base.d.ts | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/base.d.ts b/base.d.ts index 953c940..ad26053 100644 --- a/base.d.ts +++ b/base.d.ts @@ -87,7 +87,14 @@ export type ParseOptions = { //=> {foo: ['1', '2', '3']} ``` */ - readonly arrayFormat?: 'bracket' | 'index' | 'comma' | 'separator' | 'bracket-separator' | 'colon-list-separator' | 'none'; + readonly arrayFormat?: + | 'bracket' + | 'index' + | 'comma' + | 'separator' + | 'bracket-separator' + | 'colon-list-separator' + | 'none'; /** The character used to separate array elements when using `{arrayFormat: 'separator'}`. @@ -175,7 +182,9 @@ export type ParseOptions = { Use this feature to override the type for a value. This can be useful when the type is ambiguous such as a phone number (see example 1 and 2). - NOTE: array types (`string[]` and `number[]`) will not work if `arrayFormat` is set to `none`. + 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). + + NOTE: array types (`string[]` and `number[]`) will have no effect if `arrayFormat` is set to `none` (see example 5). @default {} @@ -233,12 +242,25 @@ export type ParseOptions = { //=> {age: 40, id: '01234', zipcode: '90210 } ``` + @example + Array types will have no effect when `arrayFormat` is set to `none` + ``` + queryString.parse('ids=001%2C002%2C003&foods=apple%2Corange%2Cmango', { + arrayFormat: 'none', + types: { + ids: 'number[]', + foods: 'string[]', + }, + } + //=> {ids:'001,002,003', foods:'apple,orange,mango'} + ``` + @example Parse a query utilizing all types: ``` import queryString from 'query-string'; - queryString.parse('ids=001%2C002%2C003&items=1%2C2%2C3&price=22%2E00&numbers=1%2C2%2C3&double=5&number=20', { + queryString.parse('?ids=001%2C002%2C003&items=1%2C2%2C3&price=22%2E00&numbers=1%2C2%2C3&double=5&number=20', { arrayFormat: 'comma', types: { ids: 'string', @@ -252,7 +274,10 @@ export type ParseOptions = { //=> {ids: '001,002,003', items: ['1', '2', '3'], price: '22.00', numbers: [1, 2, 3], double: 10, number: 20} ``` */ - readonly types?: Record unknown)>; + readonly types?: Record< + string, + 'number' | 'string' | 'string[]' | 'number[]' | ((value: string) => unknown) + >; }; // eslint-disable-next-line @typescript-eslint/ban-types From 858f6144fd8d5af5481f67f3820b70f30ef646fa Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Wed, 17 Jul 2024 00:53:00 +0200 Subject: [PATCH 09/11] Update base.d.ts --- base.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base.d.ts b/base.d.ts index ad26053..e5ff838 100644 --- a/base.d.ts +++ b/base.d.ts @@ -184,7 +184,7 @@ export type ParseOptions = { 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). - NOTE: array types (`string[]` and `number[]`) will have no effect if `arrayFormat` is set to `none` (see example 5). + NOTE: Array types (`string[]` and `number[]`) will have no effect if `arrayFormat` is set to `none` (see example 5). @default {} From 5106de8bcdce3dcc44da07f7f941c59da3584696 Mon Sep 17 00:00:00 2001 From: ScottEnock Date: Sun, 21 Jul 2024 18:33:15 +0100 Subject: [PATCH 10/11] feat: implement readme documentation for types feature --- base.d.ts | 2 +- readme.md | 110 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+), 1 deletion(-) diff --git a/base.d.ts b/base.d.ts index e5ff838..6bedc6d 100644 --- a/base.d.ts +++ b/base.d.ts @@ -180,7 +180,7 @@ export type ParseOptions = { /** 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`. - Use this feature to override the type for a value. This can be useful when the type is ambiguous such as a phone number (see example 1 and 2). + 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). 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). diff --git a/readme.md b/readme.md index 2bf52a1..e8beb00 100644 --- a/readme.md +++ b/readme.md @@ -224,6 +224,116 @@ queryString.parse('foo=true', {parseBooleans: true}); Parse the value as a boolean type instead of string type if it's a boolean. +##### types + +Type: `object`\ +Default: `{}` + +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`. + +Use this feature to override the type of a value. This can be useful when the type is ambiguous such as a phone number. + +It is possible to provide a custom function as the parameter type. The parameter's value will equal the function's return value. + + +Supported Types: + +- `'string'`: Parse `phoneNumber` as a string (overriding the `parseNumber` option): + +```js +import queryString from 'query-string'; + +queryString.parse('?phoneNumber=%2B380951234567&id=1', { + parseNumbers: true, + types: { + phoneNumber: 'string', + } +}); +//=> {phoneNumber: '+380951234567', id: 1} +``` + +- `'number'`: Parse `age` as a number (even when `parseNumber` is false): + +```js +import queryString from 'query-string'; + +queryString.parse('?age=20&id=01234&zipcode=90210', { + types: { + age: 'number', + } +}); +//=> {age: 20, id: '01234', zipcode: '90210 } +``` + +- `'string[]'`: Parse `items` as an array of strings (overriding the `parseNumber` option): + +```js +import queryString from 'query-string'; + +queryString.parse('?age=20&items=1%2C2%2C3', { + parseNumber: true, + types: { + items: 'string[]', + } +}); +//=> {age: 20, items: ['1', '2', '3']} +``` + +- `'number[]'`: Parse `items` as an array of numbers (even when `parseNumber` is false): + +```js +import queryString from 'query-string'; + +queryString.parse('?age=20&items=1%2C2%2C3', { + types: { + items: 'number[]', + } +}); +//=> {age: '20', items: [1, 2, 3]} +``` + +- `'Function'`: Provide a custom function as the parameter type. The parameter's value will equal the function's return value. + +```js +import queryString from 'query-string'; + +queryString.parse('?age=20&id=01234&zipcode=90210', { + types: { + age: (value) => value * 2, + } +}); +//=> {age: 40, id: '01234', zipcode: '90210 } +``` + +NOTE: Array types (`string[]` and `number[]`) will have no effect if `arrayFormat` is set to `none`. + +```js +queryString.parse('ids=001%2C002%2C003&foods=apple%2Corange%2Cmango', { + arrayFormat: 'none', + types: { + ids: 'number[]', + foods: 'string[]', + }, +} +//=> {ids:'001,002,003', foods:'apple,orange,mango'} +``` + +###### Function + +```js + +import queryString from 'query-string'; + +queryString.parse('?age=20&id=01234&zipcode=90210', { + types: { + age: (value) => value * 2, + } +}); +//=> {age: 40, id: '01234', zipcode: '90210 } +``` + +Parse the value as a boolean type instead of string type if it's a boolean. + ### .stringify(object, options?) Stringify an object into a query string and sorting the keys. From 77ffb97e6975474b5f058cf36e88d8a774a1b55d Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Mon, 22 Jul 2024 14:22:53 +0200 Subject: [PATCH 11/11] Update readme.md --- readme.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/readme.md b/readme.md index e8beb00..111f980 100644 --- a/readme.md +++ b/readme.md @@ -235,7 +235,6 @@ Use this feature to override the type of a value. This can be useful when the ty It is possible to provide a custom function as the parameter type. The parameter's value will equal the function's return value. - Supported Types: - `'string'`: Parse `phoneNumber` as a string (overriding the `parseNumber` option): @@ -321,7 +320,6 @@ queryString.parse('ids=001%2C002%2C003&foods=apple%2Corange%2Cmango', { ###### Function ```js - import queryString from 'query-string'; queryString.parse('?age=20&id=01234&zipcode=90210', {