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

Array of Objects should always have indices #422

Open
jerrens opened this issue Sep 20, 2021 · 5 comments
Open

Array of Objects should always have indices #422

jerrens opened this issue Sep 20, 2021 · 5 comments

Comments

@jerrens
Copy link

jerrens commented Sep 20, 2021

In the scenario of stringify'ing the following object:

{
    string: [ 'A', 'B', 'C'],
    obj: [
        { field1: 'A.1', field2: 'A.2' },
        { field1: 'B.1', field2: 'B.2' }
   ]
}

I would like to encode the query string as:
string=A&string=B&string=C&obj[0].field1=A.1&obj[0].field2=A.2&obj[1].field1=B.1&obj[1].field2=B.2

I've attempted to use the stringify options: { encodeValuesOnly: true, allowDots: true } along with various forms of the indices choices, but I can't seem to get the right combination.

Would it make sense if the stringify options of indices: false and/or arrayFormat: 'repeat' logic were to be updated to not include indices UNLESS the element type is an object - when it is an object, indices would need to be included because otherwise there is no way to know which fields the individual params should be reassembled into.

Current Attempts:

qs.stringify(testInput, { encodeValuesOnly: true, allowDots: true });
// Output: 'string[0]=A&string[1]=B&string[2]=C&obj[0].field1=A.1&obj[0].field2=A.2&obj[1].field1=B.1&obj[1].field2=B.2'


qs.stringify(testInput, { encodeValuesOnly: true, allowDots: true, indices: false });
// Output: 'string=A&string=B&string=C&obj.field1=A.1&obj.field2=A.2&obj.field1=B.1&obj.field2=B.2'


qs.stringify(testInput, { encodeValuesOnly: true, allowDots: true, arrayFormat: 'repeat' });
// Output: 'string=A&string=B&string=C&obj.field1=A.1&obj.field2=A.2&obj.field1=B.1&obj.field2=B.2'

In the second and third examples, are qs.parse'es back into:

{
    "string": [
        "A",
        "B",
        "C"
    ],
    "obj": {
        "field1": [
            "A.1",
            "B.1"
        ],
        "field2": [
            "A.2",
            "B.2"
        ]
    }
}

which make sense.

I have also confirmed that my desired format: string=A&string=B&string=C&obj[0].field1=A.1&obj[0].field2=A.2&obj[1].field1=B.1&obj[1].field2=B.2 does correctly parse into the original object, so no code change would be required on the parsing side.

@ljharb
Copy link
Owner

ljharb commented Sep 21, 2021

So, to clarify - you want to stringify into a mix of indices and also repeat?

What's your serverside where that's idiomatic?

@ljharb
Copy link
Owner

ljharb commented Sep 21, 2021

Note that doing something implicit here isn't an option, because [1, 'a', { b: 'c' }, { d: 'e' }] needs to be deterministically stringified in a way that matches the containing querystring.

@jerrens
Copy link
Author

jerrens commented Sep 21, 2021

Hi @ljharb - thank you looking into this. I can see the potential problem with the array of mixed data types

To answer your first question, we are making a tool that supports permalinks for the URI. One of the query parameter fields is an array of string values and a second one needs to be an array of objects because it needs both a database name and an ID for each element in the array. We also have more fields to add in the future to the tool that would likely need multiple fields grouped together. The schema for the object we want to reconstruct is the same as the one provided in the example of the OP, just the names changed to be generic values.

The permalinks work as needed when the string array in stringified to string[n]=..., but when the square brackets is encoded, it is difficult for most people to read the %5B0%5D and visualize what it is. The option to only encode the value is very nice since that lets us build a more human-readable query string for the browser, but most browsers when loading the page will encode those square brackets, making the link harder to read again. For this reason, it would be nice to be able to not have indices when not absolutely necessary on array elements that are basic data types.

I attempted to use the filter option to create my own callback to handle the encoding of the simple array, but couldn't quite figure out the proper way to return the value. Maybe this is a path to investigate more.

As for the example array of mixed datatypes you mentioned as being problematic, I image the following string would be supported by the current parser to rebuild that array properly (I'm not on my development computer at the moment to try for myself).

Pseudo code to stringify:
If the value is an array,
Loop through each element of the array
If the data type of the element is a number, string, etc, and indices = false or arrayFormat='repeat', encode as you do now (with no indices)
If the data type of the element is an object, then encode it the way you do when indices = true, using the current index of the element in the array as the reference.

For example
{ arr: [1, 'a', { b: 'c' }, { d: 'e' }]}
Would be stringified to:
arr=1&arr=a&arr[2].b=c&arr[3].d=e

@jerrens
Copy link
Author

jerrens commented Sep 21, 2021

It looks like that already works as hoped:

const qsObj = qs.parse('arr=1&arr=a&arr[2].b=c&arr[3].d=e', { allowDots: true });
JSON.stringify(qsObj, null, 4)

/*
{
    "arr": [
        "1",
        "a",
        {
            "b": "c"
        },
        {
            "d": "e"
        }
    ]
}
*/

The one exception is that the first element is a string instead of a number, but I think that will always be the case

@ljharb
Copy link
Owner

ljharb commented Sep 27, 2021

@jerrens yes, it will; all query params are always containers or strings unless you decode them differently.

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

2 participants