From b440915b007b9fc50caf7d7bfaecb5d9b8894bf3 Mon Sep 17 00:00:00 2001 From: TJ Holowaychuk Date: Wed, 29 Feb 2012 19:01:31 -0800 Subject: [PATCH 1/2] started coercion --- lib/querystring.js | 21 ++++++++++++++++++++- test/parse.js | 25 +++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/lib/querystring.js b/lib/querystring.js index ec113ad..110e10b 100644 --- a/lib/querystring.js +++ b/lib/querystring.js @@ -18,11 +18,26 @@ exports.version = '0.4.2'; var toString = Object.prototype.toString; /** - * Cache non-integer test regexp. + * Integer test regexp. */ var isint = /^[0-9]+$/; +/** + * Float test regexp. + */ + +var isfloat = /^([0-9]+)?\.[0-9]+$/; + +function coerce(str) { + if ('null' == str) return null; + if ('true' == str) return true; + if ('false' == str) return false; + if (isfloat.test(str)) return parseFloat(str, 10); + if (isint.test(str)) return parseInt(str, 10); + return str; +} + function promote(parent, key) { if (parent[key].length == 0) return parent[key] = {}; var t = {}; @@ -33,6 +48,8 @@ function promote(parent, key) { function parse(parts, parent, key, val) { var part = parts.shift(); + val = coerce(val); + // end if (!part) { if (Array.isArray(parent[key])) { @@ -234,6 +251,8 @@ function stringifyObject(obj, prefix) { function set(obj, key, val) { var v = obj[key]; + val = coerce(val); + if (undefined === v) { obj[key] = val; } else if (Array.isArray(v)) { diff --git a/test/parse.js b/test/parse.js index 99c3f09..55647d7 100644 --- a/test/parse.js +++ b/test/parse.js @@ -140,4 +140,29 @@ describe('qs.parse()', function(){ expect(qs.parse({ 'user[name]': 'tobi', 'user[email][main]': 'tobi@lb.com' })) .to.eql({ user: { name: 'tobi', email: { main: 'tobi@lb.com' } }}); }) + + describe('when "coerce" is enabled', function(){ + it('should support booleans', function(){ + expect(qs.parse('display[user][name]=true')).to.eql({ display: { user: { name: true }}}); + expect(qs.parse('foo=true')).to.eql({ foo: true }); + expect(qs.parse('foo=false')).to.eql({ foo: false }); + }) + + it('should support floats', function(){ + expect(qs.parse('foo=100.55')).to.eql({ foo: 100.55 }); + expect(qs.parse('foo=1.0')).to.eql({ foo: 1.0 }); + expect(qs.parse('foo=.5')).to.eql({ foo: .5 }); + expect(qs.parse('foo=.5xx')).to.eql({ foo: '.5xx' }); + }) + + it('should support integers', function(){ + expect(qs.parse('foo=100')).to.eql({ foo: 100 }); + expect(qs.parse('foo=0')).to.eql({ foo: 0 }); + expect(qs.parse('foo=0xff')).to.eql({ foo: '0xff' }); + }) + + it('should support null', function(){ + expect(qs.parse('foo=null')).to.eql({ foo: null }); + }) + }) }) \ No newline at end of file From a3e4221185277619346b1db6a953049320eb2caf Mon Sep 17 00:00:00 2001 From: TJ Holowaychuk Date: Wed, 29 Feb 2012 19:07:14 -0800 Subject: [PATCH 2/2] added "coerce" option --- lib/querystring.js | 38 ++++++++++++++++++++++---------------- test/parse.js | 32 +++++++++++++++++++++----------- 2 files changed, 43 insertions(+), 27 deletions(-) diff --git a/lib/querystring.js b/lib/querystring.js index 110e10b..e2a539b 100644 --- a/lib/querystring.js +++ b/lib/querystring.js @@ -46,9 +46,9 @@ function promote(parent, key) { return t; } -function parse(parts, parent, key, val) { +function parse(parts, parent, key, val, options) { var part = parts.shift(); - val = coerce(val); + if (options.coerce) val = coerce(val); // end if (!part) { @@ -76,11 +76,11 @@ function parse(parts, parent, key, val) { } else if (~part.indexOf(']')) { part = part.substr(0, part.length - 1); if (!isint.test(part) && Array.isArray(obj)) obj = promote(parent, key); - parse(parts, obj, part, val); + parse(parts, obj, part, val, options); // key } else { if (!isint.test(part) && Array.isArray(obj)) obj = promote(parent, key); - parse(parts, obj, part, val); + parse(parts, obj, part, val, options); } } } @@ -89,12 +89,12 @@ function parse(parts, parent, key, val) { * Merge parent key/val pair. */ -function merge(parent, key, val){ +function merge(parent, key, val, options){ if (~key.indexOf(']')) { var parts = key.split('[') , len = parts.length , last = len - 1; - parse(parts, parent, 'base', val); + parse(parts, parent, 'base', val, options); // optimize } else { if (!isint.test(key) && Array.isArray(parent.base)) { @@ -102,7 +102,7 @@ function merge(parent, key, val){ for (var k in parent.base) t[k] = parent.base[k]; parent.base = t; } - set(parent.base, key, val); + set(parent.base, key, val, options); } return parent; @@ -112,10 +112,10 @@ function merge(parent, key, val){ * Parse the given obj. */ -function parseObject(obj){ +function parseObject(obj, options){ var ret = { base: {} }; Object.keys(obj).forEach(function(name){ - merge(ret, name, obj[name]); + merge(ret, name, obj[name], options); }); return ret.base; } @@ -124,7 +124,7 @@ function parseObject(obj){ * Parse the given str. */ -function parseString(str){ +function parseString(str, options){ return String(str) .split('&') .reduce(function(ret, pair){ @@ -143,23 +143,29 @@ function parseString(str){ // ?foo if ('' == key) key = pair, val = ''; - return merge(ret, key, val); + return merge(ret, key, val, options); }, { base: {} }).base; } /** * Parse the given query `str` or `obj`, returning an object. * + * Options: + * + * - `coerce` when `true` js booleans, ints, and floats will be converted + * * @param {String} str | {Object} obj + * @param {Object} options * @return {Object} * @api public */ -exports.parse = function(str){ +exports.parse = function(str, options){ + options = options || {}; if (null == str || '' == str) return {}; return 'object' == typeof str - ? parseObject(str) - : parseString(str); + ? parseObject(str, options) + : parseString(str, options); }; /** @@ -249,9 +255,9 @@ function stringifyObject(obj, prefix) { * @api private */ -function set(obj, key, val) { +function set(obj, key, val, options) { var v = obj[key]; - val = coerce(val); + if (options.coerce) val = coerce(val); if (undefined === v) { obj[key] = val; diff --git a/test/parse.js b/test/parse.js index 55647d7..1a6f73d 100644 --- a/test/parse.js +++ b/test/parse.js @@ -142,27 +142,37 @@ describe('qs.parse()', function(){ }) describe('when "coerce" is enabled', function(){ + function parse(str){ + return qs.parse(str, { coerce: true }); + }; + it('should support booleans', function(){ - expect(qs.parse('display[user][name]=true')).to.eql({ display: { user: { name: true }}}); - expect(qs.parse('foo=true')).to.eql({ foo: true }); - expect(qs.parse('foo=false')).to.eql({ foo: false }); + expect(parse('display[user][name]=true')).to.eql({ display: { user: { name: true }}}); + expect(parse('foo=true')).to.eql({ foo: true }); + expect(parse('foo=false')).to.eql({ foo: false }); }) it('should support floats', function(){ - expect(qs.parse('foo=100.55')).to.eql({ foo: 100.55 }); - expect(qs.parse('foo=1.0')).to.eql({ foo: 1.0 }); - expect(qs.parse('foo=.5')).to.eql({ foo: .5 }); - expect(qs.parse('foo=.5xx')).to.eql({ foo: '.5xx' }); + expect(parse('foo=100.55')).to.eql({ foo: 100.55 }); + expect(parse('foo=1.0')).to.eql({ foo: 1.0 }); + expect(parse('foo=.5')).to.eql({ foo: .5 }); + expect(parse('foo=.5xx')).to.eql({ foo: '.5xx' }); }) it('should support integers', function(){ - expect(qs.parse('foo=100')).to.eql({ foo: 100 }); - expect(qs.parse('foo=0')).to.eql({ foo: 0 }); - expect(qs.parse('foo=0xff')).to.eql({ foo: '0xff' }); + expect(parse('foo=100')).to.eql({ foo: 100 }); + expect(parse('foo=0')).to.eql({ foo: 0 }); + expect(parse('foo=0xff')).to.eql({ foo: '0xff' }); }) it('should support null', function(){ - expect(qs.parse('foo=null')).to.eql({ foo: null }); + expect(parse('foo=null')).to.eql({ foo: null }); + }) + }) + + describe('when "coerce" is disabled', function(){ + it('should not convert values', function(){ + expect(qs.parse('foo=100')).to.eql({ foo: '100' }); }) }) }) \ No newline at end of file