Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Option to preserve empty arrays and objects at "qs.stringify()" #362

Closed
vdenisenko-waverley opened this issue Apr 29, 2020 · 32 comments
Closed

Comments

@vdenisenko-waverley
Copy link

Documentation saying:

Key with no values (such as an empty object or array) will return nothing

However there are cases when this behavior is not desirable. It brings inconsistencies between stringify and parse methods, meaning same object can't be stringified and then parsed. Example:

qs.stringify({ foo: [], bar: 1 })
// -> "bar=1"

qs.parse("bar=1")
// -> {bar: "1"}

From above example foo field is lost.

@ljharb
Copy link
Owner

ljharb commented May 2, 2020

This is how most backends interpret it - an empty array means there's nothing there, so there's nothing lost. Can you elaborate on the use cases?

@vdenisenko-waverley
Copy link
Author

vdenisenko-waverley commented May 2, 2020

I'm trying to persist query string which contains complex JSON structure on page refresh. This is why inconsistency matters for my case.

Currently I have to iterate through all JSON fields recursively and append missing fields, to properly initialize JSON on page refresh - which looks not clean and brings unnecessary complication to process.

@ljharb
Copy link
Owner

ljharb commented May 2, 2020

Is there a reason why the code interacting with your complex structure can't create the structure when needed, rather than requiring it be initialized already?

@vdenisenko-waverley
Copy link
Author

This is exactly what I have to do, just noticed it is strange that I can't get back same object which was stringified.

@ljharb
Copy link
Owner

ljharb commented May 3, 2020

JSON itself doesn't provide that guarantee; generally stringify(parse(stringify(x)) === stringify(x), but not necessarily parse(stringify(parse(x))) giving you the same object as parse(x).

@ljharb ljharb changed the title Option to perserve empty arrays and objects at "qs.stringify()" Option to preserve empty arrays and objects at "qs.stringify()" May 3, 2020
starsirius added a commit to starsirius/metaphysics that referenced this issue May 4, 2020
qs ignores empty array/object and prevents us from sending `?array[]=`.
This is a workaround to map an empty array to `[null]` so it gets treated
as an empty string, and utilize qs `filter` function to map the value.

A regression test was included.

ljharb/qs#362
@shashi8shekhar
Copy link

Did Anybody find workaround for this issue. my use case is, i am trying to render client using parsed data stored in url, i need to preserve all the state during qs.stringify().

@nikkwong
Copy link

This is necessary for cases like the mongo{ query: {$eq: []}}

@damonblack
Copy link

Seems like sending a query argument that might be empty (like an array of filter fields) is a pretty common use case. I'm fighting with this now.

@ljharb
Copy link
Owner

ljharb commented Jun 17, 2020

I could see a case where foo: [] serializes to foo[] - no equals sign - but i can't see how it would make sense in anything but the "brackets" and "comma" arrayFormat cases, since there's no way to represent an empty array otherwise.

@lvdang
Copy link

lvdang commented Jun 30, 2020

I am having the same issue. Was there a fix to this?

@lvdang
Copy link

lvdang commented Jun 30, 2020

@damonblack @nikkwong @vdenisenko-waverley - if you pass in JSON.stringify([]) this worked for me.

const query = { showItems: JSON.stringify([])}

@jmbtutor
Copy link

I'm working on a project where the query parameters are used to specify overrides, falling back to defaults for missing parameters. In this scenario, foo=[] (where [] represents an empty array) tells the backend to use an empty array for foo; this is distinct from omitting foo, which tells the backend to use defaults (and this default is not empty). We've worked around this for now by doing our own processing on a string value (joining/splitting on a delimiter with the empty string representing an empty array), but it would be nice to have this handled by the library.

@ljharb
Copy link
Owner

ljharb commented Dec 23, 2020

@jmbtutor would serializing to just foo[], without an equals sign, be sufficient to convey that semantic?

If so, I'd be willing to accept a PR for an option to both parse and stringify that explicitly preserved empty arrays and objects in this manner.

@Notmeomg
Copy link

I ran into a case where I'm using Joi array conditionals to control the attributes returned by the model. When there's no attributes in the req we use defaults, when the attributes array is empty we don't return any attributes, only associations. Was surprised to find out we couldn't pass empty arrays with qs. Is there a better solution to passing empty arrays in the query?

const query = { where: { id: 101 }, attributes: [], include: ['addresses'], }

@wisetwo
Copy link

wisetwo commented Jan 4, 2021

This is how most backends interpret it - an empty array means there's nothing there, so there's nothing lost. Can you elaborate on the use cases?

Sometimes the backend parse the query string to know what was requested, for example, an update request needs to know which field needs updating; If a field of array needs to be cleared, the backend might be expecting list: [], but the result might not be achieved if the library just drop this field.

@ljharb
Copy link
Owner

ljharb commented Jan 4, 2021

@wisetwo in which of these scenarios can the backend not be changed to adapt to the frontend (which it should always do, since the frontend should never have to change for the sake of the backend)?

@jmbtutor
Copy link

would serializing to just foo[], without an equals sign, be sufficient to convey that semantic?

@ljharb Sorry, I just noticed this ping. As long as that deserializes to an empty array, I think that should be fine. It communicates that the option is specified and that the value is empty.

@webdev-lee
Copy link

webdev-lee commented May 22, 2021

I've just spent a few hours debugging this exact issue, I send an object containing various properties to my back end, some of which are arrays, in the case that these are empty they are essentially being ignored. I do some processing on the object and then return the modified object back to my front end. With the empty arrays now missing my reactive Vue JS templates on the front end were erroring as I was checking the length of the arrays, which in some instances no longer exist.

I think there are some pretty solid use cases for not ignoring empty arrays. The workaround is having to check if these properties exist and then append empty arrays, ideally I would just be returning all the original object properties.

@iMoses-Apiiro
Copy link

Turns out that in ASP.net if you define a query parameter from type dictionary and don't pass a value it will automatically be populated by the all query string options. This is a horrible behaviour which they claim is a bug and not a feature :|
https://stackoverflow.com/questions/45997570/asp-net-core-fromquery-getting-invalid-parameters-with-dot-sign-inside/46003081#46003081

Nonetheless, it'd be very helpful if qs define an option to treat empty values (objects, array) the same as null and output an empty reference. Very very helpful...

@zcrkey

This comment has been minimized.

@ljharb

This comment has been minimized.

@jose-codaio
Copy link

At the very least, how about parametrizing the behavior to use an empty string '' instead of getting rid of the argument altogether? For my use case, I can work with a 0-value like '' but not with an undefined value.

This is still not ideal. The ideal would be that encoding and decoding could happen back and forth without surprises.

@ljharb
Copy link
Owner

ljharb commented Jan 20, 2022

@jose-codaio #362 (comment) already says i'd accept a PR that added an option.

Surprises are always inevitable, because everybody's expectations are different.

@jose-codaio
Copy link

@jose-codaio #362 (comment) already says i'd accept a PR that added an option.

Sorry, missed that.

Surprises are always inevitable, because everybody's expectations are different.

By "surprises", I meant anything preventing the encoding process from being reversible. If an encoding process isn't reversible, then it just adds burden onto the requester. It'd be awesome if I could write the following with no problem.

stringify(parse(stringify(parse(...))))

I understand if we can't always live up to that, but that is the ideal. Otherwise, I'll need custom logic to handle these "surprises".

JSON itself doesn't provide that guarantee; generally stringify(parse(stringify(x)) === stringify(x), but not necessarily parse(stringify(parse(x))) giving you the same object as parse(x).

Out of curiosity: unless x includes non-JSON aspects (eg functions, non-enumerable properties), why would that not work?

@ljharb
Copy link
Owner

ljharb commented Jan 21, 2022

What is a "non-JSON aspect"? Try round-tripping a Date object, or a regex, or anything with a .toJSON method.

It's just not a reasonable expectation that a serialize/deserialize function is reversible for all input - only for a subset of input. It's a great goal, but it's not a reasonable expectation.

@jose-codaio
Copy link

What is a "non-JSON aspect"? Try round-tripping a Date object, or a regex, or anything with a .toJSON method.

Dates and regex's are not JSON. JSON is pretty much just:

  • numbers
  • strings
  • booleans
  • arrays
  • objects
  • null

(Wikipedia, ECMA publication)

That method is misleading, but it's probably just to easily dump an object into a JSON-compatible format.

It's just not a reasonable expectation that a serialize/deserialize function is reversible for all input - only for a subset of input. It's a great goal, but it's not a reasonable expectation.

Yeah, that's fair. 😔

@zizhuxuaner

This comment was marked as off-topic.

@ljharb

This comment was marked as resolved.

@HyopeR
Copy link

HyopeR commented Oct 28, 2022

I'm having the same problem. I solve my problem by doing a little trick on the frontend. But a parameter would be nice to have empty arrays behave differently.

@ljharb
Copy link
Owner

ljharb commented Oct 30, 2022

@HyopeR #362 (comment) is still waiting for a PR.

@honia19
Copy link

honia19 commented Nov 17, 2023

Please make a PR, it is very strange functionality

@ljharb
Copy link
Owner

ljharb commented Jan 28, 2024

Fixed in #487.

@ljharb ljharb closed this as completed Jan 28, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests