Coder Social home page Coder Social logo

ljharb / qs Goto Github PK

View Code? Open in Web Editor NEW
8.4K 69.0 730.0 11.7 MB

A querystring parser with nesting support

License: BSD 3-Clause "New" or "Revised" License

JavaScript 100.00%
javascript encoding querystrings nodejs node browsers url-parsing stringify parse

qs's Introduction

qs

qs Version Badge

github actions coverage License Downloads CII Best Practices

npm badge

A querystring parsing and stringifying library with some added security.

Lead Maintainer: Jordan Harband

The qs module was originally created and maintained by TJ Holowaychuk.

Usage

var qs = require('qs');
var assert = require('assert');

var obj = qs.parse('a=c');
assert.deepEqual(obj, { a: 'c' });

var str = qs.stringify(obj);
assert.equal(str, 'a=c');

Parsing Objects

qs.parse(string, [options]);

qs allows you to create nested objects within your query strings, by surrounding the name of sub-keys with square brackets []. For example, the string 'foo[bar]=baz' converts to:

assert.deepEqual(qs.parse('foo[bar]=baz'), {
    foo: {
        bar: 'baz'
    }
});

When using the plainObjects option the parsed value is returned as a null object, created via Object.create(null) and as such you should be aware that prototype methods will not exist on it and a user may set those names to whatever value they like:

var nullObject = qs.parse('a[hasOwnProperty]=b', { plainObjects: true });
assert.deepEqual(nullObject, { a: { hasOwnProperty: 'b' } });

By default parameters that would overwrite properties on the object prototype are ignored, if you wish to keep the data from those fields either use plainObjects as mentioned above, or set allowPrototypes to true which will allow user input to overwrite those properties. WARNING It is generally a bad idea to enable this option as it can cause problems when attempting to use the properties that have been overwritten. Always be careful with this option.

var protoObject = qs.parse('a[hasOwnProperty]=b', { allowPrototypes: true });
assert.deepEqual(protoObject, { a: { hasOwnProperty: 'b' } });

URI encoded strings work too:

assert.deepEqual(qs.parse('a%5Bb%5D=c'), {
    a: { b: 'c' }
});

You can also nest your objects, like 'foo[bar][baz]=foobarbaz':

assert.deepEqual(qs.parse('foo[bar][baz]=foobarbaz'), {
    foo: {
        bar: {
            baz: 'foobarbaz'
        }
    }
});

By default, when nesting objects qs will only parse up to 5 children deep. This means if you attempt to parse a string like 'a[b][c][d][e][f][g][h][i]=j' your resulting object will be:

var expected = {
    a: {
        b: {
            c: {
                d: {
                    e: {
                        f: {
                            '[g][h][i]': 'j'
                        }
                    }
                }
            }
        }
    }
};
var string = 'a[b][c][d][e][f][g][h][i]=j';
assert.deepEqual(qs.parse(string), expected);

This depth can be overridden by passing a depth option to qs.parse(string, [options]):

var deep = qs.parse('a[b][c][d][e][f][g][h][i]=j', { depth: 1 });
assert.deepEqual(deep, { a: { b: { '[c][d][e][f][g][h][i]': 'j' } } });

The depth limit helps mitigate abuse when qs is used to parse user input, and it is recommended to keep it a reasonably small number.

For similar reasons, by default qs will only parse up to 1000 parameters. This can be overridden by passing a parameterLimit option:

var limited = qs.parse('a=b&c=d', { parameterLimit: 1 });
assert.deepEqual(limited, { a: 'b' });

To bypass the leading question mark, use ignoreQueryPrefix:

var prefixed = qs.parse('?a=b&c=d', { ignoreQueryPrefix: true });
assert.deepEqual(prefixed, { a: 'b', c: 'd' });

An optional delimiter can also be passed:

var delimited = qs.parse('a=b;c=d', { delimiter: ';' });
assert.deepEqual(delimited, { a: 'b', c: 'd' });

Delimiters can be a regular expression too:

var regexed = qs.parse('a=b;c=d,e=f', { delimiter: /[;,]/ });
assert.deepEqual(regexed, { a: 'b', c: 'd', e: 'f' });

Option allowDots can be used to enable dot notation:

var withDots = qs.parse('a.b=c', { allowDots: true });
assert.deepEqual(withDots, { a: { b: 'c' } });

Option decodeDotInKeys can be used to decode dots in keys Note: it implies allowDots, so parse will error if you set decodeDotInKeys to true, and allowDots to false.

var withDots = qs.parse('name%252Eobj.first=John&name%252Eobj.last=Doe', { decodeDotInKeys: true });
assert.deepEqual(withDots, { 'name.obj': { first: 'John', last: 'Doe' }});

Option allowEmptyArrays can be used to allowing empty array values in object

var withEmptyArrays = qs.parse('foo[]&bar=baz', { allowEmptyArrays: true });
assert.deepEqual(withEmptyArrays, { foo: [], bar: 'baz' });

Option duplicates can be used to change the behavior when duplicate keys are encountered

assert.deepEqual(qs.parse('foo=bar&foo=baz'), { foo: ['bar', 'baz'] });
assert.deepEqual(qs.parse('foo=bar&foo=baz', { duplicates: 'combine' }), { foo: ['bar', 'baz'] });
assert.deepEqual(qs.parse('foo=bar&foo=baz', { duplicates: 'first' }), { foo: 'bar' });
assert.deepEqual(qs.parse('foo=bar&foo=baz', { duplicates: 'last' }), { foo: 'baz' });

If you have to deal with legacy browsers or services, there's also support for decoding percent-encoded octets as iso-8859-1:

var oldCharset = qs.parse('a=%A7', { charset: 'iso-8859-1' });
assert.deepEqual(oldCharset, { a: '§' });

Some services add an initial utf8=✓ value to forms so that old Internet Explorer versions are more likely to submit the form as utf-8. Additionally, the server can check the value against wrong encodings of the checkmark character and detect that a query string or application/x-www-form-urlencoded body was not sent as utf-8, eg. if the form had an accept-charset parameter or the containing page had a different character set.

qs supports this mechanism via the charsetSentinel option. If specified, the utf8 parameter will be omitted from the returned object. It will be used to switch to iso-8859-1/utf-8 mode depending on how the checkmark is encoded.

Important: When you specify both the charset option and the charsetSentinel option, the charset will be overridden when the request contains a utf8 parameter from which the actual charset can be deduced. In that sense the charset will behave as the default charset rather than the authoritative charset.

var detectedAsUtf8 = qs.parse('utf8=%E2%9C%93&a=%C3%B8', {
    charset: 'iso-8859-1',
    charsetSentinel: true
});
assert.deepEqual(detectedAsUtf8, { a: 'ø' });

// Browsers encode the checkmark as ✓ when submitting as iso-8859-1:
var detectedAsIso8859_1 = qs.parse('utf8=%26%2310003%3B&a=%F8', {
    charset: 'utf-8',
    charsetSentinel: true
});
assert.deepEqual(detectedAsIso8859_1, { a: 'ø' });

If you want to decode the &#...; syntax to the actual character, you can specify the interpretNumericEntities option as well:

var detectedAsIso8859_1 = qs.parse('a=%26%239786%3B', {
    charset: 'iso-8859-1',
    interpretNumericEntities: true
});
assert.deepEqual(detectedAsIso8859_1, { a: '☺' });

It also works when the charset has been detected in charsetSentinel mode.

Parsing Arrays

qs can also parse arrays using a similar [] notation:

var withArray = qs.parse('a[]=b&a[]=c');
assert.deepEqual(withArray, { a: ['b', 'c'] });

You may specify an index as well:

var withIndexes = qs.parse('a[1]=c&a[0]=b');
assert.deepEqual(withIndexes, { a: ['b', 'c'] });

Note that the only difference between an index in an array and a key in an object is that the value between the brackets must be a number to create an array. When creating arrays with specific indices, qs will compact a sparse array to only the existing values preserving their order:

var noSparse = qs.parse('a[1]=b&a[15]=c');
assert.deepEqual(noSparse, { a: ['b', 'c'] });

You may also use allowSparse option to parse sparse arrays:

var sparseArray = qs.parse('a[1]=2&a[3]=5', { allowSparse: true });
assert.deepEqual(sparseArray, { a: [, '2', , '5'] });

Note that an empty string is also a value, and will be preserved:

var withEmptyString = qs.parse('a[]=&a[]=b');
assert.deepEqual(withEmptyString, { a: ['', 'b'] });

var withIndexedEmptyString = qs.parse('a[0]=b&a[1]=&a[2]=c');
assert.deepEqual(withIndexedEmptyString, { a: ['b', '', 'c'] });

qs will also limit specifying indices in an array to a maximum index of 20. Any array members with an index of greater than 20 will instead be converted to an object with the index as the key. This is needed to handle cases when someone sent, for example, a[999999999] and it will take significant time to iterate over this huge array.

var withMaxIndex = qs.parse('a[100]=b');
assert.deepEqual(withMaxIndex, { a: { '100': 'b' } });

This limit can be overridden by passing an arrayLimit option:

var withArrayLimit = qs.parse('a[1]=b', { arrayLimit: 0 });
assert.deepEqual(withArrayLimit, { a: { '1': 'b' } });

To disable array parsing entirely, set parseArrays to false.

var noParsingArrays = qs.parse('a[]=b', { parseArrays: false });
assert.deepEqual(noParsingArrays, { a: { '0': 'b' } });

If you mix notations, qs will merge the two items into an object:

var mixedNotation = qs.parse('a[0]=b&a[b]=c');
assert.deepEqual(mixedNotation, { a: { '0': 'b', b: 'c' } });

You can also create arrays of objects:

var arraysOfObjects = qs.parse('a[][b]=c');
assert.deepEqual(arraysOfObjects, { a: [{ b: 'c' }] });

Some people use comma to join array, qs can parse it:

var arraysOfObjects = qs.parse('a=b,c', { comma: true })
assert.deepEqual(arraysOfObjects, { a: ['b', 'c'] })

(this cannot convert nested objects, such as a={b:1},{c:d})

Parsing primitive/scalar values (numbers, booleans, null, etc)

By default, all values are parsed as strings. This behavior will not change and is explained in issue #91.

var primitiveValues = qs.parse('a=15&b=true&c=null');
assert.deepEqual(primitiveValues, { a: '15', b: 'true', c: 'null' });

If you wish to auto-convert values which look like numbers, booleans, and other values into their primitive counterparts, you can use the query-types Express JS middleware which will auto-convert all request query parameters.

Stringifying

qs.stringify(object, [options]);

When stringifying, qs by default URI encodes output. Objects are stringified as you would expect:

assert.equal(qs.stringify({ a: 'b' }), 'a=b');
assert.equal(qs.stringify({ a: { b: 'c' } }), 'a%5Bb%5D=c');

This encoding can be disabled by setting the encode option to false:

var unencoded = qs.stringify({ a: { b: 'c' } }, { encode: false });
assert.equal(unencoded, 'a[b]=c');

Encoding can be disabled for keys by setting the encodeValuesOnly option to true:

var encodedValues = qs.stringify(
    { a: 'b', c: ['d', 'e=f'], f: [['g'], ['h']] },
    { encodeValuesOnly: true }
);
assert.equal(encodedValues,'a=b&c[0]=d&c[1]=e%3Df&f[0][0]=g&f[1][0]=h');

This encoding can also be replaced by a custom encoding method set as encoder option:

var encoded = qs.stringify({ a: { b: 'c' } }, { encoder: function (str) {
    // Passed in values `a`, `b`, `c`
    return // Return encoded string
}})

(Note: the encoder option does not apply if encode is false)

Analogue to the encoder there is a decoder option for parse to override decoding of properties and values:

var decoded = qs.parse('x=z', { decoder: function (str) {
    // Passed in values `x`, `z`
    return // Return decoded string
}})

You can encode keys and values using different logic by using the type argument provided to the encoder:

var encoded = qs.stringify({ a: { b: 'c' } }, { encoder: function (str, defaultEncoder, charset, type) {
    if (type === 'key') {
        return // Encoded key
    } else if (type === 'value') {
        return // Encoded value
    }
}})

The type argument is also provided to the decoder:

var decoded = qs.parse('x=z', { decoder: function (str, defaultDecoder, charset, type) {
    if (type === 'key') {
        return // Decoded key
    } else if (type === 'value') {
        return // Decoded value
    }
}})

Examples beyond this point will be shown as though the output is not URI encoded for clarity. Please note that the return values in these cases will be URI encoded during real usage.

When arrays are stringified, they follow the arrayFormat option, which defaults to indices:

qs.stringify({ a: ['b', 'c', 'd'] });
// 'a[0]=b&a[1]=c&a[2]=d'

You may override this by setting the indices option to false, or to be more explicit, the arrayFormat option to repeat:

qs.stringify({ a: ['b', 'c', 'd'] }, { indices: false });
// 'a=b&a=c&a=d'

You may use the arrayFormat option to specify the format of the output array:

qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'indices' })
// 'a[0]=b&a[1]=c'
qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'brackets' })
// 'a[]=b&a[]=c'
qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'repeat' })
// 'a=b&a=c'
qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'comma' })
// 'a=b,c'

Note: when using arrayFormat set to 'comma', you can also pass the commaRoundTrip option set to true or false, to append [] on single-item arrays, so that they can round trip through a parse.

When objects are stringified, by default they use bracket notation:

qs.stringify({ a: { b: { c: 'd', e: 'f' } } });
// 'a[b][c]=d&a[b][e]=f'

You may override this to use dot notation by setting the allowDots option to true:

qs.stringify({ a: { b: { c: 'd', e: 'f' } } }, { allowDots: true });
// 'a.b.c=d&a.b.e=f'

You may encode the dot notation in the keys of object with option encodeDotInKeys by setting it to true: Note: it implies allowDots, so stringify will error if you set decodeDotInKeys to true, and allowDots to false. Caveat: when encodeValuesOnly is true as well as encodeDotInKeys, only dots in keys and nothing else will be encoded.

qs.stringify({ "name.obj": { "first": "John", "last": "Doe" } }, { allowDots: true, encodeDotInKeys: true })
// 'name%252Eobj.first=John&name%252Eobj.last=Doe'

You may allow empty array values by setting the allowEmptyArrays option to true:

qs.stringify({ foo: [], bar: 'baz' }, { allowEmptyArrays: true });
// 'foo[]&bar=baz'

Empty strings and null values will omit the value, but the equals sign (=) remains in place:

assert.equal(qs.stringify({ a: '' }), 'a=');

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

assert.equal(qs.stringify({ a: [] }), '');
assert.equal(qs.stringify({ a: {} }), '');
assert.equal(qs.stringify({ a: [{}] }), '');
assert.equal(qs.stringify({ a: { b: []} }), '');
assert.equal(qs.stringify({ a: { b: {}} }), '');

Properties that are set to undefined will be omitted entirely:

assert.equal(qs.stringify({ a: null, b: undefined }), 'a=');

The query string may optionally be prepended with a question mark:

assert.equal(qs.stringify({ a: 'b', c: 'd' }, { addQueryPrefix: true }), '?a=b&c=d');

The delimiter may be overridden with stringify as well:

assert.equal(qs.stringify({ a: 'b', c: 'd' }, { delimiter: ';' }), 'a=b;c=d');

If you only want to override the serialization of Date objects, you can provide a serializeDate option:

var date = new Date(7);
assert.equal(qs.stringify({ a: date }), 'a=1970-01-01T00:00:00.007Z'.replace(/:/g, '%3A'));
assert.equal(
    qs.stringify({ a: date }, { serializeDate: function (d) { return d.getTime(); } }),
    'a=7'
);

You may use the sort option to affect the order of parameter keys:

function alphabeticalSort(a, b) {
    return a.localeCompare(b);
}
assert.equal(qs.stringify({ a: 'c', z: 'y', b : 'f' }, { sort: alphabeticalSort }), 'a=c&b=f&z=y');

Finally, you can use the filter option to restrict which keys will be included in the stringified output. If you pass a function, it will be called for each key to obtain the replacement value. Otherwise, if you pass an array, it will be used to select properties and array indices for stringification:

function filterFunc(prefix, value) {
    if (prefix == 'b') {
        // Return an `undefined` value to omit a property.
        return;
    }
    if (prefix == 'e[f]') {
        return value.getTime();
    }
    if (prefix == 'e[g][0]') {
        return value * 2;
    }
    return value;
}
qs.stringify({ a: 'b', c: 'd', e: { f: new Date(123), g: [2] } }, { filter: filterFunc });
// 'a=b&c=d&e[f]=123&e[g][0]=4'
qs.stringify({ a: 'b', c: 'd', e: 'f' }, { filter: ['a', 'e'] });
// 'a=b&e=f'
qs.stringify({ a: ['b', 'c', 'd'], e: 'f' }, { filter: ['a', 0, 2] });
// 'a[0]=b&a[2]=d'

You could also use filter to inject custom serialization for user defined types. Consider you're working with some api that expects query strings of the format for ranges:

https://domain.com/endpoint?range=30...70

For which you model as:

class Range {
    constructor(from, to) {
        this.from = from;
        this.to = to;
    }
}

You could inject a custom serializer to handle values of this type:

qs.stringify(
    {
        range: new Range(30, 70),
    },
    {
        filter: (prefix, value) => {
            if (value instanceof Range) {
                return `${value.from}...${value.to}`;
            }
            // serialize the usual way
            return value;
        },
    }
);
// range=30...70

Handling of null values

By default, null values are treated like empty strings:

var withNull = qs.stringify({ a: null, b: '' });
assert.equal(withNull, 'a=&b=');

Parsing does not distinguish between parameters with and without equal signs. Both are converted to empty strings.

var equalsInsensitive = qs.parse('a&b=');
assert.deepEqual(equalsInsensitive, { a: '', b: '' });

To distinguish between null values and empty strings use the strictNullHandling flag. In the result string the null values have no = sign:

var strictNull = qs.stringify({ a: null, b: '' }, { strictNullHandling: true });
assert.equal(strictNull, 'a&b=');

To parse values without = back to null use the strictNullHandling flag:

var parsedStrictNull = qs.parse('a&b=', { strictNullHandling: true });
assert.deepEqual(parsedStrictNull, { a: null, b: '' });

To completely skip rendering keys with null values, use the skipNulls flag:

var nullsSkipped = qs.stringify({ a: 'b', c: null}, { skipNulls: true });
assert.equal(nullsSkipped, 'a=b');

If you're communicating with legacy systems, you can switch to iso-8859-1 using the charset option:

var iso = qs.stringify({ æ: 'æ' }, { charset: 'iso-8859-1' });
assert.equal(iso, '%E6=%E6');

Characters that don't exist in iso-8859-1 will be converted to numeric entities, similar to what browsers do:

var numeric = qs.stringify({ a: '☺' }, { charset: 'iso-8859-1' });
assert.equal(numeric, 'a=%26%239786%3B');

You can use the charsetSentinel option to announce the character by including an utf8=✓ parameter with the proper encoding if the checkmark, similar to what Ruby on Rails and others do when submitting forms.

var sentinel = qs.stringify({ a: '☺' }, { charsetSentinel: true });
assert.equal(sentinel, 'utf8=%E2%9C%93&a=%E2%98%BA');

var isoSentinel = qs.stringify({ a: 'æ' }, { charsetSentinel: true, charset: 'iso-8859-1' });
assert.equal(isoSentinel, 'utf8=%26%2310003%3B&a=%E6');

Dealing with special character sets

By default the encoding and decoding of characters is done in utf-8, and iso-8859-1 support is also built in via the charset parameter.

If you wish to encode querystrings to a different character set (i.e. Shift JIS) you can use the qs-iconv library:

var encoder = require('qs-iconv/encoder')('shift_jis');
var shiftJISEncoded = qs.stringify({ a: 'こんにちは!' }, { encoder: encoder });
assert.equal(shiftJISEncoded, 'a=%82%B1%82%F1%82%C9%82%BF%82%CD%81I');

This also works for decoding of query strings:

var decoder = require('qs-iconv/decoder')('shift_jis');
var obj = qs.parse('a=%82%B1%82%F1%82%C9%82%BF%82%CD%81I', { decoder: decoder });
assert.deepEqual(obj, { a: 'こんにちは!' });

RFC 3986 and RFC 1738 space encoding

RFC3986 used as default option and encodes ' ' to %20 which is backward compatible. In the same time, output can be stringified as per RFC1738 with ' ' equal to '+'.

assert.equal(qs.stringify({ a: 'b c' }), 'a=b%20c');
assert.equal(qs.stringify({ a: 'b c' }, { format : 'RFC3986' }), 'a=b%20c');
assert.equal(qs.stringify({ a: 'b c' }, { format : 'RFC1738' }), 'a=b+c');

Security

Please email @ljharb or see https://tidelift.com/security if you have a potential security vulnerability to report.

qs for enterprise

Available as part of the Tidelift Subscription

The maintainers of qs and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. Learn more.

Acknowledgements

qs logo by NUMI:

NUMI Logo

qs's People

Contributors

5outh avatar agreea avatar aks- avatar albertoleal avatar alexmeah avatar arb avatar cascornelissen avatar codeclown avatar connormiha avatar daggerjames avatar dead-horse avatar designbyonyx avatar dougwilson avatar dreyks avatar elidoran avatar geek avatar hueniverse avatar idler8 avatar jamie-beck avatar jokero avatar ljharb avatar martinheidegger avatar mizozobu avatar mjackson avatar neaox avatar nlf avatar papandreou avatar pierot avatar snow01 avatar tdzienniak avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

qs's Issues

array holes incorrectly copied into object on large index

The following:

qs.parse('a[2]=2&a[999999999]=1')

results in the following

{ a: { '0': undefined, '1': undefined, '2': '2', '999999999': '1' } }

Since 0 and 1 were actually holes in the array (i.e. !(0 in arr)), they should not have properties created from them in the object. I would expect:

{ a: { '2': '2', '999999999': '1' } }

which is the output if you had them backwards: qs.parse('a[999999999]=1&a[2]=2')

TL;DR I think qs.parse('a[2]=2&a[999999999]=1') should have the same resulting object (with the same existing properties) as qs.parse('a[999999999]=1&a[2]=2').

Perhaps follow the W3C JSON forms spec?

It seems there is a technical report on adding in JSON to HTML forms: http://www.w3.org/TR/html-json-forms/

The syntax seems to pretty much be exactly what is being done here, but with slight differences (really, the differences just define the edge cases like how foo=bar&foo[0]=baz should parse: {"foo":{"":"bar","0":"baz"}}). It would also mean you have a good bullet against all the arguments that show up now and again for how this module operates :)

Error when plain object in a value

brief reproduction (from expressjs/connect-multiparty#11 (comment)):

$ node -pe 'o=Object.create(null);o.r=4;require("qs").parse({"a": o})'

node_modules\qs\lib\utils.js:47
        if (source.hasOwnProperty(i)) {
                   ^
TypeError: Object object has no method 'hasOwnProperty'
    at Object.exports.clone (node_modules\qs\lib\utils.js:47:20)
    at Object.exports.clone (node_modules\qs\lib\utils.js:48:31)
    at Object.module.exports [as parse] (node_modules\qs\lib\parse.js:141:89)
    at [eval]:1:43
    at Object.<anonymous> ([eval]-wrapper:6:22)
    at Module._compile (module.js:456:26)
    at evalScript (node.js:536:25)
    at startup (node.js:80:7)
    at node.js:906:3

In 'a&b=' treat `a` as boolean flag (true)

If no = is given for a flag, it should be handled as boolean (true).

For example:

Qs.parse('a&b=', {allowBoolean: true});
//  {a: true, b: ''}

without the allowBoolean option, it should not do this conversion (as it does today):

Qs.parse('a&b=');
//  {a: '', b: ''}

Array of objects parsing

I'm trying to parse an IPN message from PayPal, and it has an array of objects in a query string:

transaction%5B0%5D.is_primary_receiver=true&transaction%5B0%5D.id_for_sender_txn=5GN104358V194442M&log_default_shipping_address_in_transaction=false&transaction%5B0%5D.receiver=seller1.test%40mercher.net&action_type=PAY&ipn_notification_url=http%3A//staging.mercherdev.com/ipn&transaction%5B1%5D.paymentType=SERVICE&transaction%5B0%5D.amount=USD+136.25&charset=windows-1252&transaction_type=Adaptive+Payment+PAY&transaction%5B1%5D.id_for_sender_txn=9L346159YT798301J&transaction%5B1%5D.is_primary_receiver=false&transaction%5B0%5D.status=Completed&notify_version=UNVERSIONED&transaction%5B0%5D.id=3D429284U5773243B&cancel_url=http%3A//staging.mercherdev.com/test&transaction%5B1%5D.status_for_sender_txn=Completed&transaction%5B1%5D.receiver=dmitriy.s.les-facilitator%40gmail.com&verify_sign=AFcWxV21C7fd0v3bYYYRCpSSRl31Aaz5mmivhYQQvF-Zz1csgdIo0LYX&sender_email=buyer1.test%40mercher.net&fees_payer=PRIMARYRECEIVER&transaction%5B0%5D.status_for_sender_txn=Completed&return_url=http%3A//staging.mercherdev.com/test&transaction%5B0%5D.paymentType=GOODS&transaction%5B1%5D.amount=USD+2.50&reverse_all_parallel_payments_on_error=true&tracking_id=32&transaction%5B1%5D.pending_reason=NONE&pay_key=AP-8LF09716FJ765462U&transaction%5B1%5D.id=43253900X5221472M&transaction%5B0%5D.pending_reason=NONE&status=COMPLETED&transaction%5B1%5D.status=Completed&test_ipn=1&payment_request_date=Mon+Oct+06+09%3A16%3A01+PDT+2014

Qs.parse() gives me this result:

{
    "transaction":                                 [ "NONE", "Completed" ],
    "log_default_shipping_address_in_transaction": "false",
    "action_type":                                 "PAY",
    "ipn_notification_url":                        "http://staging.mercherdev.com/ipn",
    "charset":                                     "windows-1252",
    "transaction_type":                            "Adaptive Payment PAY",
    "notify_version":                              "UNVERSIONED",
    "cancel_url":                                  "http://staging.mercherdev.com/test",
    "verify_sign":                                 "AFcWxV21C7fd0v3bYYYRCpSSRl31Aaz5mmivhYQQvF-Zz1csgdIo0LYX",
    "sender_email":                                "[email protected]",
    "fees_payer":                                  "PRIMARYRECEIVER",
    "return_url":                                  "http://staging.mercherdev.com/test",
    "reverse_all_parallel_payments_on_error":      "true",
    "tracking_id":                                 "32",
    "pay_key":                                     "AP-8LF09716FJ765462U",
    "status":                                      "COMPLETED",
    "test_ipn":                                    "1",
    "payment_request_date":                        "Mon Oct 06 09:16:01 PDT 2014"
}

while I was expecting for:

{
    "transaction":                                 [
        {
            "is_primary_receiver":   "true",
            "id_for_sender_txn":     "1EN35283R74763703",
            "receiver":              "[email protected]",
            "amount":                "USD 107.00",
            "status":                "Completed",
            "id":                    "90E58532R7615693H",
            "status_for_sender_txn": "Completed",
            "paymentType":           "GOODS",
            "pending_reason":        "NONE"
        },
        {
            "paymentType":           "SERVICE",
            "id_for_sender_txn":     "7XF42668MS476925V",
            "is_primary_receiver":   "false",
            "status_for_sender_txn": "Completed",
            "receiver":              "[email protected]",
            "amount":                "USD 2.00",
            "pending_reason":        "NONE",
            "id":                    "6XR12273235492049",
            "status":                "Completed"
        }
    ],
    "log_default_shipping_address_in_transaction": "false",
    "action_type":                                 "PAY",
    "ipn_notification_url":                        "http://staging.mercherdev.com/ipn",
    "charset":                                     "windows-1252",
    "transaction_type":                            "Adaptive Payment PAY",
    "notify_version":                              "UNVERSIONED",
    "cancel_url":                                  "http://staging.mercherdev.com/test",
    "verify_sign":                                 "AFcWxV21C7fd0v3bYYYRCpSSRl31AW50ZrxhpTVKQjEObfEFfTcdVouU",
    "sender_email":                                "[email protected]",
    "fees_payer":                                  "PRIMARYRECEIVER",
    "return_url":                                  "http://staging.mercherdev.com/test",
    "reverse_all_parallel_payments_on_error":      "true",
    "tracking_id":                                 "31",
    "pay_key":                                     "AP-10U799054T2106106",
    "status":                                      "COMPLETED",
    "test_ipn":                                    "1",
    "payment_request_date":                        "Mon Oct 06 09:00:28 PDT 2014"
}

Is there any way to achieve this? I'm using v2.2.4

Problems with non-zero-based names

Heys guys.

When I have a input like:

Qs.parse('obj[0]=value&obj[1]=value')

I get the expected array value

{obj: ['value', 'value']};

But when the input doesn't start with a zero based index, for instance:

Qs.parse('obj[1]=value&obj[2]=value')

I would expect a return value such as:

{obj: {'1': 'value', '2': 'value'}};

But I still get the same result as before:

{obj: ['value', 'value']};

Reference: expressjs/body-parser#40

Option to dedupe multiple keys with the same name

I couldn't find it in the readme but does this module support deduping params?

I was expecting if I send a=true&a=false I would get a: 'false'

But currently I get a: [ 'true', 'false']

Is this something qs supports? If not would you be open to supporting it?

Parser return a array with empty string even if there is no value to the parameter

Hi,
I noticed a change in the behavior of this library compared to the one it replaces (https://github.com/visionmedia/node-querystring)

The previous :
console.log(parse('param[]=')) // log []

This one :
console.log(parse('param[]=')) // log ['']

To me the previous behavior made more sense.
Now even if the param has no value the array returned by the parser has a length of 1

Can you confirm that this behavior is wanted ?

Disable array parsing

So I wanted to disable array parsing by doing arrayLimit: 0, but apparently it will still create arrays when the index is 0, which is oddly misleading :)

$ node -pe 'require("qs").parse("a[0]=b", {arrayLimit: 0})'
{ a: [ 'b' ] }

But I figured out I could do arrayLimit: -1 and it would work, so I hope it's an official option :)

$ node -pe 'require("qs").parse("a[0]=b", {arrayLimit: -1})'
{ a: { '0': 'b' } }

But now I'm running into the problem where a person can specify a negative number and turn my objects into arrays anyway, and the only way to get around that is ridiculous:

$ node -pe 'require("qs").parse("a[-1]=b", {arrayLimit: -1})'
{ a: [] }
$ node -pe 'require("qs").parse("a[-1]=b", {arrayLimit: -Infinity})'
{ a: { '-1': 'b' } }

Bad parse when turning array into object

The following shows that when a key starts as an array, values that match Object.prototype keys are basically treated as if they are just an empty bracket for some reason. I would not expect the 1 property to exist in the example below:

$ node -pe 'require("qs").parse("a[]=b&a[hasOwnProperty]=c&a[x]=y")'
{ a: { '0': 'b', '1': 'c', x: 'y' } }

$ node -pe 'require("qs/package").version'
2.4.1

Is there an alternative to Buffer.isBuffer?

https://github.com/hapijs/qs/blob/master/lib/utils.js#L134 contains a call to Buffer.isBuffer. This means that when [email protected] is browserified (we're still on 0.6 for the client side...), the whole Buffer module is pulled in - which adds an extra 40kb (~20kb minified) of code in the bundle - and all of it for just this one function, which is only used once.

I'll try to see if there's a workaround on the browserify side of things, but the question still stands...

The workaround in browserify is to create a fake file which exports Buffer.isBuffer = () => false and expose that as "buffer". It would still be nice to not have to use this workaround...

parser discards first empty value in array

Hi, I use this awesome library. But there is one wonder point.

var qs = require("qs");
console.dir(qs.parse("x[]=")); // case1
console.dir(qs.parse("a[]=&a[]=b&a[]=c")); // case2
console.dir(qs.parse("a[]=b&a[]=&a[]=c")); // case3
console.dir(qs.parse("a[]=b&a[]=c&a[]=")); // case4
{ x: [ '' ] }
{ a: [ 'b', 'c' ] }
{ a: [ 'b', '', 'c' ] }
{ a: [ 'b', 'c', '' ] }

Is case2 expected value?

qs.parse silently drops properties

I'm just opening a new issue so that old one does not get lost. This is a general continuation as a new issue from #73 (comment) and onwards.

Because qs silently discards properties, it makes this library unsuitable for any real REST API. It only works for "toy" web sites that have a very explicit list of keys in all routes. With this module, one cannot make a route that contains customer-defined properties, as they will evidently choose one that is silently dropped just because traffic happens to flow through Node.js.

The main other parsers doesn't particularly have this issue:

$ node -pe 'JSON.parse('"'"'{"hasOwnProperty":"yes"}'"'"')'
{ hasOwnProperty: 'yes' }

$ node -pe 'require("querystring").parse("hasOwnProperty=yes")'
{ hasOwnProperty: 'yes' }

$ node -pe 'require("qs").parse("hasOwnProperty=yes")'
{}

merge fails to overwrite properties

Hi there,

I stumbled upon this in remix-run/react-router#599 - I cannot override properites using lib/utils:merge:

> merge({a:'a'}, {a:'b'})
{ a: 'a' }

// triggers different code path 
// https://github.com/hapijs/qs/blob/9250c4cda5102fcf72441445816e6d311fc6813d/lib/utils.js#L25
> merge({a:'a'}, {a:undefined}) 
{ a: 'a' }

Is this desired behaviour? If not, any idea for a workaround?

Thanks!
Max

Add option to sort object keys

Consider adding an option to sort object keys. This could be useful when using query strings for (or as part of) a unique key, ensuring the order of property declaration doesn't affect output.

qs.stringify({ a: 'b', z: 'x' }) === 'a=b&z=x';
qs.stringify({ z: 'x', a: 'b' }) === 'z=x&a=b';
qs.stringify({ z: 'x', a: 'b' }, { sort: true }) === 'a=b&z=x';

I can work on this and provide pull request if okay.

Number parsing

Hi,

Would it be possible to get a number when possible, instead of a string?
For this query string: ?a=2, we currently get { a: '2' }. This would be awesome to have { a: 2 }.

Thanks!

Why is there arrayLimit?

I am not really sure why you are using this limit. There is no option to change this value. Do you have a good reason for this? Because I have JSON structure with list of pictures greater than 20

Ability to not limit parameters?

So I just noticed this when comparing the options of qs to querystring: querystring accepts 0 or Infinity to say no limit on the keys (though it by default has no limit). Providing either of those two values to this module results in an empty parse. I'm not sure if you want to support those (the 0 thing seems dumb to indicate "no limit", but whatever), but thought I'd bring it up :)

Remove references to Buffer

References to the Buffer global variable assume the environment is node.js, but it's a shame to limit the use of this package to node environments when it could easily be used in browsers as well.

Can we modify these checks to make qs more browser-friendly?

What kind of license does qs have?

I see the license text and it seems okay, I apparently just have to include it if I modify the source and of course add the license.

But is there an official name for the license? It doesn't seem to be MIT or Apache or BSD.

Option to omit array indices

I would like to have the option to omit array indices from the stringify. That is:

qs.stringify({ a: ['b', 'c'] }, { indices: false }); // should return a=b&a=c

This is important for formatting requests that are compatible with the Google APIs which don't accept array indices in their request query parameters.

Better API for inflating first-level object keys?

Hi, so an issue was brought up in expressjs/connect-multiparty#11 earlier today. Basically it boils down to a need for a real object conversion API here, I think. I would like to be able to pass in an object and have this library expand out just the keys into a rich object and leave the values alone.

Basically I would like to have something that takes

{
  "user[name]": {"pop[bob]": 3},
  "user[email]": null
}

and gives

{
  "user": {
    "name": {"pop[bob]": 3},
    "email": null
  }
}

and it only looks at the first level keys and does not look down into the objects. I hope I'm making sense, I'm in a rush :)

bower.json

Since this module can be browserified and used in the browser, would be nice to be available through bower, and have a dist folder with the already browserified package.

qs.parse stackoverflow on circular objects

So when using qs.parse and passing in an object to expand out the top-level keys, if somewhere in the value is a circular reference, it will fail with RangeError: Maximum call stack size exceeded. Here is a simple example:

var a = {}; a.b = a;
qs.parse({
  'foo[bar]' = 'baz',
  'foo[baz]' = a
})

I'm really only reporting this because it used to work with the old qs library. It was reported to me here: expressjs/connect-multiparty#11 (comment)

Add option to disable "Transform dot notation to bracket notation"

Can we get an option to disable the new "Transform dot notation to bracket notation" functionality? We would like to keep our code base current with the latest version of this module, but we do NOT want to allow users to submit this new syntax to our API. Since this is done just through a .replace, it seems like it would be trivial to add a switch that would skip that transformation (but it can still be enabled by default, of course).

Cannot parse mix of simple array and explicit array.

The following testcase would fail:

    it('parses mix of simple and explicit array', function (done) {

        expect(Qs.parse('a=b&a[]=b')).to.deep.equal({ a: ['b', 'b'] });
        expect(Qs.parse('a[]=b&a=b')).to.deep.equal({ a: ['b', 'b'] });
        done();
    });

Not sure if above is valid testcase to be honest.

Empty values of a POST array disappear after being submitted

Following my conversation with @blipsofadoug on Twitter

He says that It is an issue when you are using "extended" urlencoded body parsing

Empty values of a POST array disappear after being submitted.

HTML :

<form action="" method="post">
        <input type="text" name="data[]" value="snow" />
        <input type="text" name="data[]" value="" />
        <input type="text" name="data[]" value="hey oh" />
        <input type="submit" value="send me the sausage" />
</form>

in php : print_r($_POST['data'])

Array ( [0] => snow [1] => [2] => hey oh )

in nodejs console.log(req.body.data)

[ 'snow', 'hey oh' ]

Failure parsing url escaped data from windows

Hi,

a windows client sends me the following sequence:

%f6%e4%fc%e1%20

Javascripts unescape can handle this sequence,
Qs can't. I think it's ucs2 encoding.

A spec would look like this:

qs = require('qs')

describe 'Qs', ->

  describe '#parse', ->
    it 'parses ucs2 from windows', (done) ->
      expect(unescape('%f6%e4%fc%e1%20')).to.be.like('öäüá ')
      expect(qs.parse('a=%f6%e4%fc%e1%20')).to.be.like( {'a': 'öäüá '})
      done()

Is this to be considered a Qs-Bug?

Regards
Dieter

Changelog? Semver?

Hi, I noticed this went from 1.2.2 to 2.1.0 and via semver conventions that would imply a breaking change? Though I don't see any Changelog - any reason there's not one? And can I safely update to the new version seamlessly or do I need to change my code/use of qs appropriately?
Thanks.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.