diff --git a/.eslintrc b/.eslintrc index e2cade5e..76f8e1cd 100644 --- a/.eslintrc +++ b/.eslintrc @@ -4,12 +4,12 @@ "extends": "@ljharb", "rules": { - "complexity": [2, 26], + "complexity": [2, 27], "consistent-return": 1, "id-length": [2, { "min": 1, "max": 25, "properties": "never" }], "indent": [2, 4], "max-params": [2, 12], - "max-statements": [2, 43], + "max-statements": [2, 44], "no-continue": 1, "no-magic-numbers": 0, "no-restricted-syntax": [2, "BreakStatement", "DebuggerStatement", "ForInStatement", "LabeledStatement", "WithStatement"], diff --git a/README.md b/README.md index 4fe81eae..d999ddae 100644 --- a/README.md +++ b/README.md @@ -118,6 +118,13 @@ var limited = qs.parse('a=b&c=d', { parameterLimit: 1 }); assert.deepEqual(limited, { a: 'b' }); ``` +To bypass the leading question mark, use `ignoreQueryPrefix`: + +```javascript +var prefixed = qs.parse('?a=b&c=d', { ignoreQueryPrefix: true }); +assert.deepEqual(prefixed, { a: 'b', c: 'd' }); +``` + An optional delimiter can also be passed: ```javascript @@ -323,6 +330,12 @@ 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: + +```javascript +assert.equal(qs.stringify({ a: 'b', c: 'd' }, { addQueryPrefix: true }), '?a=b&c=d'); +``` + The delimiter may be overridden with stringify as well: ```javascript diff --git a/lib/parse.js b/lib/parse.js index 3a0e185f..597ba0cf 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -18,7 +18,9 @@ var defaults = { var parseValues = function parseQueryStringValues(str, options) { var obj = {}; - var parts = str.split(options.delimiter, options.parameterLimit === Infinity ? undefined : options.parameterLimit); + var cleanStr = options.ignoreQueryPrefix ? str.replace(/^\?/, '') : str; + var limit = options.parameterLimit === Infinity ? undefined : options.parameterLimit; + var parts = cleanStr.split(options.delimiter, limit); for (var i = 0; i < parts.length; ++i) { var part = parts[i]; @@ -136,6 +138,7 @@ module.exports = function (str, opts) { throw new TypeError('Decoder has to be a function.'); } + options.ignoreQueryPrefix = options.ignoreQueryPrefix === true; options.delimiter = typeof options.delimiter === 'string' || utils.isRegExp(options.delimiter) ? options.delimiter : defaults.delimiter; options.depth = typeof options.depth === 'number' ? options.depth : defaults.depth; options.arrayLimit = typeof options.arrayLimit === 'number' ? options.arrayLimit : defaults.arrayLimit; diff --git a/lib/stringify.js b/lib/stringify.js index 8fbbe4ba..9a351111 100644 --- a/lib/stringify.js +++ b/lib/stringify.js @@ -203,5 +203,7 @@ module.exports = function (object, opts) { )); } - return keys.join(delimiter); + var prefix = options.addQueryPrefix === true ? '?' : ''; + + return prefix + keys.join(delimiter); }; diff --git a/test/parse.js b/test/parse.js index a92ea15c..a4b2418a 100644 --- a/test/parse.js +++ b/test/parse.js @@ -305,6 +305,13 @@ test('parse()', function (t) { st.end(); }); + t.test('allows for query string prefix', function (st) { + st.deepEqual(qs.parse('?foo=bar', { ignoreQueryPrefix: true }), { foo: 'bar' }); + st.deepEqual(qs.parse('foo=bar', { ignoreQueryPrefix: true }), { foo: 'bar' }); + st.deepEqual(qs.parse('?foo=bar', { ignoreQueryPrefix: false }), { '?foo': 'bar' }); + st.end(); + }); + t.test('parses an object', function (st) { var input = { 'user[name]': { 'pop[bob]': 3 }, diff --git a/test/stringify.js b/test/stringify.js index b06446c8..5d4f68be 100644 --- a/test/stringify.js +++ b/test/stringify.js @@ -18,6 +18,11 @@ test('stringify()', function (t) { st.end(); }); + t.test('adds query prefix', function (st) { + st.equal(qs.stringify({ a: 'b' }, { addQueryPrefix: true }), '?a=b'); + st.end(); + }); + t.test('stringifies a nested object', function (st) { st.equal(qs.stringify({ a: { b: 'c' } }), 'a%5Bb%5D=c'); st.equal(qs.stringify({ a: { b: { c: { d: 'e' } } } }), 'a%5Bb%5D%5Bc%5D%5Bd%5D=e');