qs 0.3.1 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: node_js
2
+ node_js:
3
+ - 0.6
4
+ - 0.4
package/History.md CHANGED
@@ -1,4 +1,25 @@
1
1
 
2
+ 0.4.2 / 2012-02-08
3
+ ==================
4
+
5
+ * Fixed: ensure objects are created when appropriate not arrays [aheckmann]
6
+
7
+ 0.4.1 / 2012-01-26
8
+ ==================
9
+
10
+ * Fixed stringify()ing numbers. Closes #23
11
+
12
+ 0.4.0 / 2011-11-21
13
+ ==================
14
+
15
+ * Allow parsing of an existing object (for `bodyParser()`) [jackyz]
16
+ * Replaced expresso with mocha
17
+
18
+ 0.3.2 / 2011-11-08
19
+ ==================
20
+
21
+ * Fixed global variable leak
22
+
2
23
  0.3.1 / 2011-08-17
3
24
  ==================
4
25
 
package/Makefile CHANGED
@@ -1,7 +1,5 @@
1
1
 
2
2
  test:
3
- @./node_modules/.bin/expresso \
4
- -I support \
5
- -I lib
3
+ @./node_modules/.bin/mocha
6
4
 
7
5
  .PHONY: test
package/Readme.md CHANGED
@@ -6,18 +6,23 @@
6
6
 
7
7
  $ npm install qs
8
8
 
9
-
10
9
  ## Examples
11
10
 
12
- require('qs').parse('user[name][first]=tj&user[email]=tj');
13
- // => { user: { name: { first: 'tj' }, email: 'tj' } }
11
+ ```js
12
+ var qs = require('qs');
13
+
14
+ qs.parse('user[name][first]=Tobi&user[email]=tobi@learnboost.com');
15
+ // => { user: { name: { first: 'Tobi' }, email: 'tobi@learnboost.com' } }
16
+
17
+ qs.stringify({ user: { name: 'Tobi', email: 'tobi@learnboost.com' }})
18
+ // => user[name]=Tobi&user[email]=tobi%40learnboost.com
19
+ ```
14
20
 
15
21
  ## Testing
16
22
 
17
- Update git submodules:
23
+ Install dev dependencies:
18
24
 
19
- $ git submodule init
20
- $ git submodule update
25
+ $ npm install -d
21
26
 
22
27
  and execute:
23
28
 
package/examples.js CHANGED
@@ -46,3 +46,6 @@ console.log(obj)
46
46
 
47
47
  var obj = qs.parse('user[0]=tj&user[foo]=TJ');
48
48
  console.log(obj)
49
+
50
+ var str = qs.stringify({ user: { name: 'Tobi', email: 'tobi@learnboost.com' }});
51
+ console.log(str);
@@ -1,3 +1,4 @@
1
+
1
2
  /*!
2
3
  * querystring
3
4
  * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
@@ -8,7 +9,7 @@
8
9
  * Library version.
9
10
  */
10
11
 
11
- exports.version = '0.3.1';
12
+ exports.version = '0.4.2';
12
13
 
13
14
  /**
14
15
  * Object#toString() ref for stringify().
@@ -20,31 +21,97 @@ var toString = Object.prototype.toString;
20
21
  * Cache non-integer test regexp.
21
22
  */
22
23
 
23
- var notint = /[^0-9]/;
24
+ var isint = /^[0-9]+$/;
25
+
26
+ function promote(parent, key) {
27
+ if (parent[key].length == 0) return parent[key] = {};
28
+ var t = {};
29
+ for (var i in parent[key]) t[i] = parent[key][i];
30
+ parent[key] = t;
31
+ return t;
32
+ }
33
+
34
+ function parse(parts, parent, key, val) {
35
+ var part = parts.shift();
36
+ // end
37
+ if (!part) {
38
+ if (Array.isArray(parent[key])) {
39
+ parent[key].push(val);
40
+ } else if ('object' == typeof parent[key]) {
41
+ parent[key] = val;
42
+ } else if ('undefined' == typeof parent[key]) {
43
+ parent[key] = val;
44
+ } else {
45
+ parent[key] = [parent[key], val];
46
+ }
47
+ // array
48
+ } else {
49
+ var obj = parent[key] = parent[key] || [];
50
+ if (']' == part) {
51
+ if (Array.isArray(obj)) {
52
+ if ('' != val) obj.push(val);
53
+ } else if ('object' == typeof obj) {
54
+ obj[Object.keys(obj).length] = val;
55
+ } else {
56
+ obj = parent[key] = [parent[key], val];
57
+ }
58
+ // prop
59
+ } else if (~part.indexOf(']')) {
60
+ part = part.substr(0, part.length - 1);
61
+ if (!isint.test(part) && Array.isArray(obj)) obj = promote(parent, key);
62
+ parse(parts, obj, part, val);
63
+ // key
64
+ } else {
65
+ if (!isint.test(part) && Array.isArray(obj)) obj = promote(parent, key);
66
+ parse(parts, obj, part, val);
67
+ }
68
+ }
69
+ }
24
70
 
25
71
  /**
26
- * Parse the given query `str`, returning an object.
27
- *
28
- * @param {String} str
29
- * @return {Object}
30
- * @api public
72
+ * Merge parent key/val pair.
31
73
  */
32
74
 
33
- exports.parse = function(str){
34
- if (null == str || '' == str) return {};
35
-
36
- function promote(parent, key) {
37
- if (parent[key].length == 0) return parent[key] = {};
38
- var t = {};
39
- for (var i in parent[key]) t[i] = parent[key][i];
40
- parent[key] = t;
41
- return t;
75
+ function merge(parent, key, val){
76
+ if (~key.indexOf(']')) {
77
+ var parts = key.split('[')
78
+ , len = parts.length
79
+ , last = len - 1;
80
+ parse(parts, parent, 'base', val);
81
+ // optimize
82
+ } else {
83
+ if (!isint.test(key) && Array.isArray(parent.base)) {
84
+ var t = {};
85
+ for (var k in parent.base) t[k] = parent.base[k];
86
+ parent.base = t;
87
+ }
88
+ set(parent.base, key, val);
42
89
  }
43
90
 
91
+ return parent;
92
+ }
93
+
94
+ /**
95
+ * Parse the given obj.
96
+ */
97
+
98
+ function parseObject(obj){
99
+ var ret = { base: {} };
100
+ Object.keys(obj).forEach(function(name){
101
+ merge(ret, name, obj[name]);
102
+ });
103
+ return ret.base;
104
+ }
105
+
106
+ /**
107
+ * Parse the given str.
108
+ */
109
+
110
+ function parseString(str){
44
111
  return String(str)
45
112
  .split('&')
46
113
  .reduce(function(ret, pair){
47
- try{
114
+ try{
48
115
  pair = decodeURIComponent(pair.replace(/\+/g, ' '));
49
116
  } catch(e) {
50
117
  // ignore
@@ -54,69 +121,28 @@ exports.parse = function(str){
54
121
  , brace = lastBraceInKey(pair)
55
122
  , key = pair.substr(0, brace || eql)
56
123
  , val = pair.substr(brace || eql, pair.length)
57
- , val = val.substr(val.indexOf('=') + 1, val.length)
58
- , parent = ret;
124
+ , val = val.substr(val.indexOf('=') + 1, val.length);
59
125
 
60
126
  // ?foo
61
127
  if ('' == key) key = pair, val = '';
62
128
 
63
- // nested
64
- if (~key.indexOf(']')) {
65
- var parts = key.split('[')
66
- , len = parts.length
67
- , last = len - 1;
68
-
69
- function parse(parts, parent, key) {
70
- var part = parts.shift();
71
-
72
- // end
73
- if (!part) {
74
- if (Array.isArray(parent[key])) {
75
- parent[key].push(val);
76
- } else if ('object' == typeof parent[key]) {
77
- parent[key] = val;
78
- } else if ('undefined' == typeof parent[key]) {
79
- parent[key] = val;
80
- } else {
81
- parent[key] = [parent[key], val];
82
- }
83
- // array
84
- } else {
85
- obj = parent[key] = parent[key] || [];
86
- if (']' == part) {
87
- if (Array.isArray(obj)) {
88
- if ('' != val) obj.push(val);
89
- } else if ('object' == typeof obj) {
90
- obj[Object.keys(obj).length] = val;
91
- } else {
92
- obj = parent[key] = [parent[key], val];
93
- }
94
- // prop
95
- } else if (~part.indexOf(']')) {
96
- part = part.substr(0, part.length - 1);
97
- if(notint.test(part) && Array.isArray(obj)) obj = promote(parent, key);
98
- parse(parts, obj, part);
99
- // key
100
- } else {
101
- if(notint.test(part) && Array.isArray(obj)) obj = promote(parent, key);
102
- parse(parts, obj, part);
103
- }
104
- }
105
- }
106
-
107
- parse(parts, parent, 'base');
108
- // optimize
109
- } else {
110
- if (notint.test(key) && Array.isArray(parent.base)) {
111
- var t = {};
112
- for(var k in parent.base) t[k] = parent.base[k];
113
- parent.base = t;
114
- }
115
- set(parent.base, key, val);
116
- }
129
+ return merge(ret, key, val);
130
+ }, { base: {} }).base;
131
+ }
117
132
 
118
- return ret;
119
- }, {base: {}}).base;
133
+ /**
134
+ * Parse the given query `str` or `obj`, returning an object.
135
+ *
136
+ * @param {String} str | {Object} obj
137
+ * @return {Object}
138
+ * @api public
139
+ */
140
+
141
+ exports.parse = function(str){
142
+ if (null == str || '' == str) return {};
143
+ return 'object' == typeof str
144
+ ? parseObject(str)
145
+ : parseString(str);
120
146
  };
121
147
 
122
148
  /**
@@ -135,7 +161,7 @@ var stringify = exports.stringify = function(obj, prefix) {
135
161
  } else if ('string' == typeof obj) {
136
162
  return stringifyString(obj, prefix);
137
163
  } else {
138
- return prefix;
164
+ return prefix + '=' + obj;
139
165
  }
140
166
  };
141
167
 
@@ -184,12 +210,14 @@ function stringifyObject(obj, prefix) {
184
210
  var ret = []
185
211
  , keys = Object.keys(obj)
186
212
  , key;
213
+
187
214
  for (var i = 0, len = keys.length; i < len; ++i) {
188
215
  key = keys[i];
189
216
  ret.push(stringify(obj[key], prefix
190
217
  ? prefix + '[' + encodeURIComponent(key) + ']'
191
218
  : encodeURIComponent(key)));
192
219
  }
220
+
193
221
  return ret.join('&');
194
222
  }
195
223
 
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "qs",
3
3
  "description": "querystring parser",
4
- "version": "0.3.1",
4
+ "version": "0.4.2",
5
5
  "repository": {
6
6
  "type" : "git",
7
7
  "url" : "git://github.com/visionmedia/node-querystring.git"
8
8
  },
9
9
  "devDependencies": {
10
- "expresso": "0.9.2"
10
+ "mocha": "*"
11
11
  , "should": "*"
12
12
  },
13
13
  "author": "TJ Holowaychuk <tj@vision-media.ca> (http://tjholowaychuk.com)",
@@ -0,0 +1,2 @@
1
+ --require should
2
+ --ui exports
@@ -3,8 +3,7 @@
3
3
  * Module dependencies.
4
4
  */
5
5
 
6
- var qs = require('../')
7
- , should = require('should');
6
+ var qs = require('../');
8
7
 
9
8
  module.exports = {
10
9
  'test basics': function(){
@@ -92,8 +91,12 @@ module.exports = {
92
91
 
93
92
  qs.parse('user[name][first]=tj&user[name][first]=TJ')
94
93
  .should.eql({ user: { name: { first: ['tj', 'TJ'] }}});
94
+
95
+ var o = qs.parse('existing[fcbaebfecc][name][last]=tj')
96
+ o.should.eql({ existing: { 'fcbaebfecc': { name: { last: 'tj' }}}})
97
+ Array.isArray(o.existing).should.be.false;
95
98
  },
96
-
99
+
97
100
  'test right-hand brackets': function(){
98
101
  qs.parse('pets=["tobi"]')
99
102
  .should.eql({ pets: '["tobi"]' });
@@ -140,6 +143,14 @@ module.exports = {
140
143
  'test malformed uri': function(){
141
144
  qs.parse('{%:%}').should.eql({ '{%:%}': '' });
142
145
  qs.parse('foo=%:%}').should.eql({ 'foo': '%:%}' });
146
+ },
147
+
148
+ 'test semi-parsed': function(){
149
+ qs.parse({ 'user[name]': 'tobi' })
150
+ .should.eql({ user: { name: 'tobi' }});
151
+
152
+ qs.parse({ 'user[name]': 'tobi', 'user[email][main]': 'tobi@lb.com' })
153
+ .should.eql({ user: { name: 'tobi', email: { main: 'tobi@lb.com' } }});
143
154
  }
144
155
 
145
156
  // 'test complex': function(){
@@ -0,0 +1,103 @@
1
+
2
+ /**
3
+ * Module dependencies.
4
+ */
5
+
6
+ var qs = require('../')
7
+ , should = require('should')
8
+ , str_identities = {
9
+ 'basics': [
10
+ { str: 'foo=bar', obj: {'foo' : 'bar'}},
11
+ { str: 'foo=%22bar%22', obj: {'foo' : '\"bar\"'}},
12
+ { str: 'foo=', obj: {'foo': ''}},
13
+ { str: 'foo=1&bar=2', obj: {'foo' : '1', 'bar' : '2'}},
14
+ { str: 'my%20weird%20field=q1!2%22\'w%245%267%2Fz8)%3F', obj: {'my weird field': "q1!2\"'w$5&7/z8)?"}},
15
+ { str: 'foo%3Dbaz=bar', obj: {'foo=baz': 'bar'}},
16
+ { str: 'foo=bar&bar=baz', obj: {foo: 'bar', bar: 'baz'}}
17
+ ],
18
+ 'escaping': [
19
+ { str: 'foo=foo%20bar', obj: {foo: 'foo bar'}},
20
+ { str: 'cht=p3&chd=t%3A60%2C40&chs=250x100&chl=Hello%7CWorld', obj: {
21
+ cht: 'p3'
22
+ , chd: 't:60,40'
23
+ , chs: '250x100'
24
+ , chl: 'Hello|World'
25
+ }}
26
+ ],
27
+ 'nested': [
28
+ { str: 'foo[]=bar&foo[]=quux', obj: {'foo' : ['bar', 'quux']}},
29
+ { str: 'foo[]=bar', obj: {foo: ['bar']}},
30
+ { str: 'foo[]=1&foo[]=2', obj: {'foo' : ['1', '2']}},
31
+ { str: 'foo=bar&baz[]=1&baz[]=2&baz[]=3', obj: {'foo' : 'bar', 'baz' : ['1', '2', '3']}},
32
+ { str: 'foo[]=bar&baz[]=1&baz[]=2&baz[]=3', obj: {'foo' : ['bar'], 'baz' : ['1', '2', '3']}},
33
+ { str: 'x[y][z]=1', obj: {'x' : {'y' : {'z' : '1'}}}},
34
+ { str: 'x[y][z][]=1', obj: {'x' : {'y' : {'z' : ['1']}}}},
35
+ { str: 'x[y][z]=2', obj: {'x' : {'y' : {'z' : '2'}}}},
36
+ { str: 'x[y][z][]=1&x[y][z][]=2', obj: {'x' : {'y' : {'z' : ['1', '2']}}}},
37
+ { str: 'x[y][][z]=1', obj: {'x' : {'y' : [{'z' : '1'}]}}},
38
+ { str: 'x[y][][z][]=1', obj: {'x' : {'y' : [{'z' : ['1']}]}}},
39
+ { str: 'x[y][][z]=1&x[y][][w]=2', obj: {'x' : {'y' : [{'z' : '1', 'w' : '2'}]}}},
40
+ { str: 'x[y][][v][w]=1', obj: {'x' : {'y' : [{'v' : {'w' : '1'}}]}}},
41
+ { str: 'x[y][][z]=1&x[y][][v][w]=2', obj: {'x' : {'y' : [{'z' : '1', 'v' : {'w' : '2'}}]}}},
42
+ { str: 'x[y][][z]=1&x[y][][z]=2', obj: {'x' : {'y' : [{'z' : '1'}, {'z' : '2'}]}}},
43
+ { str: 'x[y][][z]=1&x[y][][w]=a&x[y][][z]=2&x[y][][w]=3', obj: {'x' : {'y' : [{'z' : '1', 'w' : 'a'}, {'z' : '2', 'w' : '3'}]}}},
44
+ { str: 'user[name][first]=tj&user[name][last]=holowaychuk', obj: { user: { name: { first: 'tj', last: 'holowaychuk' }}}}
45
+ ],
46
+ 'errors': [
47
+ { obj: 'foo=bar', message: 'stringify expects an object' },
48
+ { obj: ['foo', 'bar'], message: 'stringify expects an object' }
49
+ ],
50
+ 'numbers': [
51
+ { str: 'limit[]=1&limit[]=2&limit[]=3', obj: { limit: [1, 2, '3'] }},
52
+ { str: 'limit=1', obj: { limit: 1 }}
53
+ ]
54
+ };
55
+
56
+
57
+ // Assert error
58
+ function err(fn, msg){
59
+ var err;
60
+ try {
61
+ fn();
62
+ } catch (e) {
63
+ should.equal(e.message, msg);
64
+ return;
65
+ }
66
+ throw new Error('no exception thrown, expected "' + msg + '"');
67
+ }
68
+
69
+ function test(type) {
70
+ var str, obj;
71
+ for (var i = 0; i < str_identities[type].length; i++) {
72
+ str = str_identities[type][i].str;
73
+ obj = str_identities[type][i].obj;
74
+ qs.stringify(obj).should.eql(str);
75
+ }
76
+ }
77
+
78
+ module.exports = {
79
+ 'test basics': function() {
80
+ test('basics');
81
+ },
82
+
83
+ 'test escaping': function() {
84
+ test('escaping');
85
+ },
86
+
87
+ 'test nested': function() {
88
+ test('nested');
89
+ },
90
+
91
+ 'test numbers': function(){
92
+ test('numbers');
93
+ },
94
+
95
+ 'test errors': function() {
96
+ var obj, message;
97
+ for (var i = 0; i < str_identities['errors'].length; i++) {
98
+ message = str_identities['errors'][i].message;
99
+ obj = str_identities['errors'][i].obj;
100
+ err(function(){ qs.stringify(obj) }, message);
101
+ }
102
+ }
103
+ };
@@ -1,95 +0,0 @@
1
-
2
- /**
3
- * Module dependencies.
4
- */
5
-
6
- var qs = require('../')
7
- , should = require('should')
8
- , query_string_identities = {
9
- 'basics': [
10
- {query_string: 'foo=bar', parsed: {'foo' : 'bar'}},
11
- {query_string: 'foo=%22bar%22', parsed: {'foo' : '\"bar\"'}},
12
- {query_string: 'foo=', parsed: {'foo': ''}},
13
- {query_string: 'foo=1&bar=2', parsed: {'foo' : '1', 'bar' : '2'}},
14
- {query_string: 'my%20weird%20field=q1!2%22\'w%245%267%2Fz8)%3F', parsed: {'my weird field': "q1!2\"'w$5&7/z8)?"}},
15
- {query_string: 'foo%3Dbaz=bar', parsed: {'foo=baz': 'bar'}},
16
- {query_string: 'foo=bar&bar=baz', parsed: {foo: 'bar', bar: 'baz'}}
17
- ],
18
- 'escaping': [
19
- {query_string: 'foo=foo%20bar', parsed: {foo: 'foo bar'}},
20
- {query_string: 'cht=p3&chd=t%3A60%2C40&chs=250x100&chl=Hello%7CWorld', parsed: {
21
- cht: 'p3'
22
- , chd: 't:60,40'
23
- , chs: '250x100'
24
- , chl: 'Hello|World'
25
- }}
26
- ],
27
- 'nested': [
28
- {query_string: 'foo[]=bar&foo[]=quux', parsed: {'foo' : ['bar', 'quux']}},
29
- {query_string: 'foo[]=bar', parsed: {foo: ['bar']}},
30
- {query_string: 'foo[]=1&foo[]=2', parsed: {'foo' : ['1', '2']}},
31
- {query_string: 'foo=bar&baz[]=1&baz[]=2&baz[]=3', parsed: {'foo' : 'bar', 'baz' : ['1', '2', '3']}},
32
- {query_string: 'foo[]=bar&baz[]=1&baz[]=2&baz[]=3', parsed: {'foo' : ['bar'], 'baz' : ['1', '2', '3']}},
33
- {query_string: 'x[y][z]=1', parsed: {'x' : {'y' : {'z' : '1'}}}},
34
- {query_string: 'x[y][z][]=1', parsed: {'x' : {'y' : {'z' : ['1']}}}},
35
- {query_string: 'x[y][z]=2', parsed: {'x' : {'y' : {'z' : '2'}}}},
36
- {query_string: 'x[y][z][]=1&x[y][z][]=2', parsed: {'x' : {'y' : {'z' : ['1', '2']}}}},
37
- {query_string: 'x[y][][z]=1', parsed: {'x' : {'y' : [{'z' : '1'}]}}},
38
- {query_string: 'x[y][][z][]=1', parsed: {'x' : {'y' : [{'z' : ['1']}]}}},
39
- {query_string: 'x[y][][z]=1&x[y][][w]=2', parsed: {'x' : {'y' : [{'z' : '1', 'w' : '2'}]}}},
40
- {query_string: 'x[y][][v][w]=1', parsed: {'x' : {'y' : [{'v' : {'w' : '1'}}]}}},
41
- {query_string: 'x[y][][z]=1&x[y][][v][w]=2', parsed: {'x' : {'y' : [{'z' : '1', 'v' : {'w' : '2'}}]}}},
42
- {query_string: 'x[y][][z]=1&x[y][][z]=2', parsed: {'x' : {'y' : [{'z' : '1'}, {'z' : '2'}]}}},
43
- {query_string: 'x[y][][z]=1&x[y][][w]=a&x[y][][z]=2&x[y][][w]=3', parsed: {'x' : {'y' : [{'z' : '1', 'w' : 'a'}, {'z' : '2', 'w' : '3'}]}}},
44
- {query_string: 'user[name][first]=tj&user[name][last]=holowaychuk', parsed: { user: { name: { first: 'tj', last: 'holowaychuk' }}}}
45
- ],
46
- 'errors': [
47
- { parsed: 'foo=bar', message: 'stringify expects an object' },
48
- { parsed: ['foo', 'bar'], message: 'stringify expects an object' }
49
- ]
50
- };
51
-
52
-
53
- // Assert error
54
- function err(fn, msg){
55
- var err;
56
- try {
57
- fn();
58
- } catch (e) {
59
- should.equal(e.message, msg);
60
- return;
61
- }
62
- throw new Error('no exception thrown, expected "' + msg + '"');
63
- }
64
-
65
- function test(type) {
66
- var str, obj;
67
- for (var i = 0; i < query_string_identities[type].length; i++) {
68
- str = query_string_identities[type][i].query_string;
69
- obj = query_string_identities[type][i].parsed;
70
- qs.stringify(obj).should.eql(str);
71
- }
72
- }
73
-
74
- module.exports = {
75
- 'test basics': function() {
76
- test('basics');
77
- },
78
-
79
- 'test escaping': function() {
80
- test('escaping');
81
- },
82
-
83
- 'test nested': function() {
84
- test('nested');
85
- },
86
-
87
- 'test errors': function() {
88
- var parsed, message;
89
- for (var i = 0; i < query_string_identities['errors'].length; i++) {
90
- message = query_string_identities['errors'][i].message;
91
- parsed = query_string_identities['errors'][i].parsed;
92
- err(function(){ qs.stringify(parsed) }, message);
93
- }
94
- }
95
- };