ljharb / qs

A querystring parser with nesting support
BSD 3-Clause "New" or "Revised" License
8.54k stars 728 forks source link

parse single value as array #434

Closed JoseVSeb closed 2 years ago

JoseVSeb commented 2 years ago

I was looking for a good query parser including URLSearchParams, query-string and qs. But each is lacking a feature that at least one other has.

Say, I have a query string "a=1" and another "a=1,2". Right now, if I use a comma-separated query parser, then the output would be {a:1} and {a:[1,2]} respectively. But I want my app to get an array regardless; like so {a:[1]} and {a:[1,2]} respectively. Sure, I could handle that in my app itself, but in real-world scenarios, I would likely have many query params; manually handling each one is not ideal, especially for something like this that should be handled by the parser.

In URLSearchParams, it's possible via getAll function instead of get, but it lacks so many other essential features like having support for comma separated parsing.

It would be great to have an option to force parse certain keys as arrays and not arrays, regardless of the default inferred format; it would be more code friendly rather than having to write exceptions for every little scenario.

ljharb commented 2 years ago

The proper way to ensure that is to have your query strings be a[]=1 and a[]=1&a[]=2, with arrayFormat brackets.

Any time you want to get an array regardless, you can do [].concat(item) (or [].concat(item || []), etc) when fetching from the object, but I think the better approach is to use the explicit bracketed form.

I could envision a parse option that takes an array of key names, and always makes those be arrays, but I'm not yet convinced that's a valuable thing to allow.

jerrylian-db commented 2 years ago

Hi @ljharb, bumping this thread. Like @JoseVSeb, I would like to have comma separated query string for stringifying arrays, because in my use-case, I have an array of many many items and I don't want the URL to be too long.

However, for an object like {a: [1]}, when I stringify it with arrayFormat: "comma" and then parse it with comma: true, it becomes {a: 1}. How can I customize the encoding logic to use bracket format for one item, but comma format for more than one item? I imagine that would fix the issue.

Would it be possible to encode using a format like "a[]=1,2" so that both single and multiple item scenarios are interpreted correctly?

jerrylian-db commented 2 years ago

Another option I'm thinking about is that when arrayFormat: "comma" is turned on, if the array length is 1, let it be encoded with the bracket format a[]=1.

Edit: some changes to these lines should suffice.

ljharb commented 2 years ago

@jerrylian-db a[]=1,2 is the string "1,2" as an array item.

Unfortunately the comma arrayFormat is a bit weird. qs.stringify({a: [1, 2]}, { arrayFormat: 'comma', encode: false }) produces 'a=1,2', and then qs.parse('a=1,2', { comma: true }) produces { a: [ '1', '2' ] } - which is correct.

However, qs.parse('a=1', { comma: true }) produces { a: '1' }, which isn't what you want. qs.parse('a=1,', { comma: true }) produces { a: [ '1', '' ] }, so that doesn't work either.

qs.parse('a[]=1', { comma: true }) does produce { a: [ '1' ] } but qs.parse('a[]=1,2', { comma: true }) produces { a: [ [ '1', '2' ] ] }.

So, it seems like perhaps stringify, with arrayFormat comma, should be turning a single-item array into a bracket-suffixed key. Would that work for your purposes? (also @JoseVSeb)

jerrylian-db commented 2 years ago

Yes, I think that would! Here's the behavior I'm looking for:

That way, arrays of all (non-zero) sizes can be properly encoded and decoded when arrayFormat='comma'.

ljharb commented 2 years ago

@jerrylian-db can you confirm #441 works for you?

jerrylian-db commented 2 years ago

@ljharb It does! Thanks so much!

jerrylian-db commented 2 years ago

@ljharb Quick question: when will these changes be released?

ljharb commented 2 years ago

There's no release schedule or anything, but I'll likely have this out soon.

ljharb commented 2 years ago

Went ahead and published v6.10.4.

jerrylian-db commented 2 years ago

@ljharb It seems that your PR has led to some errors. Now when I run qs.stringify({ a: 'c' }, { encodeValuesOnly: true, arrayFormat: 'comma' } I get a[] = c rather than a=c. Also, is there a reason you decided to only add this fix to when envodeValuesOnly is set to true?

ljharb commented 2 years ago

hmm, that does seem wrong - but the presence of encodeValuesOnly shouldn't be relevant.

I'll get a fix out.

ljharb commented 2 years ago

0e903c0 should cover this, it'll be out in v6.10.5 shortly.

ljharb commented 2 years ago

This ended up being reverted by default, but v6.11.0 adds a commaRoundTrip option to get the behavior you want.

robations commented 2 years ago
const query = "a=1&a=2,3";
Array.from(new URLSearchParams(query))
    .map((k, v] => [k, v.split(",")]);

// [
//   [ 'a', [ '1 ] ],
//   [ 'a', [ '2', '3' ] ],
// ]

This did the job for me, if anyone's looking for predictable arrays with comma parsing. YMMV.

In my project the a[] style added extra bytes we don't need.