Skip to content

Commit

Permalink
Close sindresorhus#53 PR: support object properties - fixes sindresor…
Browse files Browse the repository at this point in the history
  • Loading branch information
SamVerschueren authored and sindresorhus committed Mar 26, 2016
1 parent d72fcc1 commit 7bebd8e
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 22 deletions.
16 changes: 10 additions & 6 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,21 @@ exports.extract = function (str) {
};

exports.parse = function (str) {
// Create an object with no prototype
// https://github.com/sindresorhus/query-string/issues/47
var ret = Object.create(null);

if (typeof str !== 'string') {
return {};
return ret;
}

str = str.trim().replace(/^(\?|#|&)/, '');

if (!str) {
return {};
return ret;
}

return str.split('&').reduce(function (ret, param) {
str.split('&').forEach(function (param) {
var parts = param.replace(/\+/g, ' ').split('=');
// Firefox (pre 40) decodes `%3D` to `=`
// https://github.com/sindresorhus/query-string/pull/37
Expand All @@ -29,16 +33,16 @@ exports.parse = function (str) {
// http://w3.org/TR/2012/WD-url-20120524/#collect-url-parameters
val = val === undefined ? null : decodeURIComponent(val);

if (!ret.hasOwnProperty(key)) {
if (ret[key] !== undefined) {
ret[key] = val;
} else if (Array.isArray(ret[key])) {
ret[key].push(val);
} else {
ret[key] = [ret[key], val];
}
});

return ret;
}, {});
return ret;
};

exports.stringify = function (obj) {
Expand Down
2 changes: 2 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ console.log(location.search);

Parse a query string into an object. Leading `?` or `#` are ignored, so you can pass `location.search` or `location.hash` directly.

The returned object is created with [`Object.create(null)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create) and thus does not have a `prototype`.

### .stringify(*object*)

Stringify an object into a query string, sorting the keys.
Expand Down
51 changes: 35 additions & 16 deletions test/parse.js
Original file line number Diff line number Diff line change
@@ -1,51 +1,70 @@
import test from 'ava';
import fn from '../';

// https://github.com/sindresorhus/query-string/pull/48#issuecomment-198242935
function tsame(t, actual, expected) {
Object.keys(expected).forEach(key => {
t.is(actual[key], expected[key]);
});
}

test.beforeEach(t => {
t.context.same = tsame.bind(undefined, t);
});

test('query strings starting with a `?`', t => {
t.same(fn.parse('?foo=bar'), {foo: 'bar'});
t.context.same(fn.parse('?foo=bar'), {foo: 'bar'});
});

test('query strings starting with a `#`', t => {
t.same(fn.parse('#foo=bar'), {foo: 'bar'});
t.context.same(fn.parse('#foo=bar'), {foo: 'bar'});
});

test('query strings starting with a `&`', t => {
t.same(fn.parse('&foo=bar&foo=baz'), {foo: ['bar', 'baz']});
t.context.same(fn.parse('&foo=bar&foo=baz'), {foo: ['bar', 'baz']});
});

test('parse a query string', t => {
t.same(fn.parse('foo=bar'), {foo: 'bar'});
t.context.same(fn.parse('foo=bar'), {foo: 'bar'});
});

test('parse multiple query string', t => {
t.same(fn.parse('foo=bar&key=val'), {foo: 'bar', key: 'val'});
t.context.same(fn.parse('foo=bar&key=val'), {foo: 'bar', key: 'val'});
});

test('parse query string without a value', t => {
t.same(fn.parse('foo'), {foo: null});
t.same(fn.parse('foo&key'), {foo: null, key: null});
t.same(fn.parse('foo=bar&key'), {foo: 'bar', key: null});
t.context.same(fn.parse('foo'), {foo: null});
t.context.same(fn.parse('foo&key'), {foo: null, key: null});
t.context.same(fn.parse('foo=bar&key'), {foo: 'bar', key: null});
t.context.same(fn.parse('a&a'), {a: [null, null]});
t.context.same(fn.parse('a=&a'), {a: ['', null]});
});

test('return empty object if no qss can be found', t => {
t.same(fn.parse('?'), {});
t.same(fn.parse('&'), {});
t.same(fn.parse('#'), {});
t.same(fn.parse(' '), {});
t.context.same(fn.parse('?'), {});
t.context.same(fn.parse('&'), {});
t.context.same(fn.parse('#'), {});
t.context.same(fn.parse(' '), {});
});

test('handle `+` correctly', t => {
t.same(fn.parse('foo+faz=bar+baz++'), {'foo faz': 'bar baz '});
t.context.same(fn.parse('foo+faz=bar+baz++'), {'foo faz': 'bar baz '});
});

test('handle multiple of the same key', t => {
t.same(fn.parse('foo=bar&foo=baz'), {foo: ['bar', 'baz']});
t.context.same(fn.parse('foo=bar&foo=baz'), {foo: ['bar', 'baz']});
});

test('query strings params including embedded `=`', t => {
t.same(fn.parse('?param=http%3A%2F%2Fsomeurl%3Fid%3D2837'), {param: 'http://someurl?id=2837'});
t.context.same(fn.parse('?param=http%3A%2F%2Fsomeurl%3Fid%3D2837'), {param: 'http://someurl?id=2837'});
});

test('query strings params including raw `=`', t => {
t.same(fn.parse('?param=http://someurl?id=2837'), {param: 'http://someurl?id=2837'});
t.context.same(fn.parse('?param=http://someurl?id=2837'), {param: 'http://someurl?id=2837'});
});

test('object properties', t => {
t.notOk(fn.parse().prototype);
t.context.same(fn.parse('hasOwnProperty=foo'), {hasOwnProperty: 'fo'});
t.context.same(fn.parse('__proto__=bar'), {__proto__: 'bar'});
});

0 comments on commit 7bebd8e

Please sign in to comment.