diff --git a/.eslintrc b/.eslintrc index a9343fa6..35220cd9 100644 --- a/.eslintrc +++ b/.eslintrc @@ -14,7 +14,7 @@ "id-length": [2, { "min": 1, "max": 25, "properties": "never" }], "indent": [2, 4], "max-lines-per-function": [2, { "max": 150 }], - "max-params": [2, 15], + "max-params": [2, 16], "max-statements": [2, 53], "multiline-comment-style": 0, "no-continue": 1, diff --git a/README.md b/README.md index 01263804..28a58c62 100644 --- a/README.md +++ b/README.md @@ -402,6 +402,8 @@ 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: ```javascript diff --git a/lib/stringify.js b/lib/stringify.js index 55c37404..48ec0306 100644 --- a/lib/stringify.js +++ b/lib/stringify.js @@ -62,6 +62,7 @@ var stringify = function stringify( object, prefix, generateArrayPrefix, + commaRoundTrip, strictNullHandling, skipNulls, encoder, @@ -126,7 +127,7 @@ var stringify = function stringify( for (var i = 0; i < valuesArray.length; ++i) { valuesJoined += (i === 0 ? '' : ',') + formatter(encoder(valuesArray[i], defaults.encoder, charset, 'value', format)); } - return [formatter(keyValue) + (isArray(obj) && valuesArray.length === 1 ? '[]' : '') + '=' + valuesJoined]; + return [formatter(keyValue) + (commaRoundTrip && isArray(obj) && valuesArray.length === 1 ? '[]' : '') + '=' + valuesJoined]; } return [formatter(keyValue) + '=' + formatter(encoder(obj, defaults.encoder, charset, 'value', format))]; } @@ -150,7 +151,7 @@ var stringify = function stringify( objKeys = sort ? keys.sort(sort) : keys; } - var adjustedPrefix = generateArrayPrefix === 'comma' && isArray(obj) && obj.length === 1 ? prefix + '[]' : prefix; + var adjustedPrefix = commaRoundTrip && isArray(obj) && obj.length === 1 ? prefix + '[]' : prefix; for (var j = 0; j < objKeys.length; ++j) { var key = objKeys[j]; @@ -171,6 +172,7 @@ var stringify = function stringify( value, keyPrefix, generateArrayPrefix, + commaRoundTrip, strictNullHandling, skipNulls, encoder, @@ -267,6 +269,10 @@ module.exports = function (object, opts) { } var generateArrayPrefix = arrayPrefixGenerators[arrayFormat]; + if (opts && 'commaRoundTrip' in opts && typeof opts.commaRoundTrip !== 'boolean') { + throw new TypeError('`commaRoundTrip` must be a boolean, or absent'); + } + var commaRoundTrip = generateArrayPrefix === 'comma' && opts && opts.commaRoundTrip; if (!objKeys) { objKeys = Object.keys(obj); @@ -287,6 +293,7 @@ module.exports = function (object, opts) { obj[key], key, generateArrayPrefix, + commaRoundTrip, options.strictNullHandling, options.skipNulls, options.encode ? options.encoder : null, diff --git a/test/stringify.js b/test/stringify.js index fda52413..f0cdfefa 100644 --- a/test/stringify.js +++ b/test/stringify.js @@ -144,7 +144,8 @@ test('stringify()', function (t) { st.test('array with a single item', function (s2t) { s2t.equal(qs.stringify({ a: ['c'] }, { encodeValuesOnly: true, arrayFormat: 'indices' }), 'a[0]=c'); s2t.equal(qs.stringify({ a: ['c'] }, { encodeValuesOnly: true, arrayFormat: 'brackets' }), 'a[]=c'); - s2t.equal(qs.stringify({ a: ['c'] }, { encodeValuesOnly: true, arrayFormat: 'comma' }), 'a[]=c'); // so it parses back as an array + s2t.equal(qs.stringify({ a: ['c'] }, { encodeValuesOnly: true, arrayFormat: 'comma' }), 'a=c'); + s2t.equal(qs.stringify({ a: ['c'] }, { encodeValuesOnly: true, arrayFormat: 'comma', commaRoundTrip: true }), 'a[]=c'); // so it parses back as an array s2t.equal(qs.stringify({ a: ['c'] }, { encodeValuesOnly: true }), 'a[0]=c'); s2t.end(); @@ -379,12 +380,14 @@ test('stringify()', function (t) { st.equal(qs.stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'indices' }), 'b[0]=&c=c'); st.equal(qs.stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'brackets' }), 'b[]=&c=c'); st.equal(qs.stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'repeat' }), 'b=&c=c'); - st.equal(qs.stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'comma' }), 'b[]=&c=c'); + st.equal(qs.stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'comma' }), 'b=&c=c'); + st.equal(qs.stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'comma', commaRoundTrip: true }), 'b[]=&c=c'); // with strictNullHandling st.equal(qs.stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'indices', strictNullHandling: true }), 'b[0]&c=c'); st.equal(qs.stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'brackets', strictNullHandling: true }), 'b[]&c=c'); st.equal(qs.stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'repeat', strictNullHandling: true }), 'b&c=c'); - st.equal(qs.stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'comma', strictNullHandling: true }), 'b[]&c=c'); + st.equal(qs.stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'comma', strictNullHandling: true }), 'b&c=c'); + st.equal(qs.stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'comma', strictNullHandling: true, commaRoundTrip: true }), 'b[]&c=c'); // with skipNulls st.equal(qs.stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'indices', skipNulls: true }), 'c=c'); st.equal(qs.stringify({ a: [], b: [null], c: 'c' }, { encode: false, arrayFormat: 'brackets', skipNulls: true }), 'c=c'); @@ -712,6 +715,18 @@ test('stringify()', function (t) { arrayFormat: 'comma' } ), + 'a=' + date.getTime(), + 'works with arrayFormat comma' + ); + st.equal( + qs.stringify( + { a: [date] }, + { + serializeDate: function (d) { return d.getTime(); }, + arrayFormat: 'comma', + commaRoundTrip: true + } + ), 'a%5B%5D=' + date.getTime(), 'works with arrayFormat comma' );