qs 6.1.0 → 6.2.1

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/.eslintrc CHANGED
@@ -4,10 +4,12 @@
4
4
  "extends": "@ljharb",
5
5
 
6
6
  "rules": {
7
- "complexity": [2, 19],
7
+ "complexity": [2, 22],
8
8
  "consistent-return": [1],
9
+ "id-length": [2, { "min": 1, "max": 25, "properties": "never" }],
10
+ "indent": [2, 4],
9
11
  "max-params": [2, 9],
10
- "max-statements": [2, 33],
12
+ "max-statements": [2, 36],
11
13
  "no-extra-parens": [1],
12
14
  "no-continue": [1],
13
15
  "no-magic-numbers": 0,
package/CHANGELOG.md CHANGED
@@ -1,4 +1,16 @@
1
- ## [**6.1.0**](https://github.com/ljharb/qs/issues?milestone=34&state=closed)
1
+ ## **6.2.1**
2
+ - [Fix] ensure `key[]=x&key[]&key[]=y` results in 3, not 2, values
3
+ - [Refactor] Be explicit and use `Object.prototype.hasOwnProperty.call`
4
+ - [Tests] remove `parallelshell` since it does not reliably report failures
5
+ - [Tests] up to `node` `v6.3`, `v5.12`
6
+ - [Dev Deps] update `tape`, `eslint`, `@ljharb/eslint-config`, `qs-iconv`
7
+
8
+ ## [**6.2.0**](https://github.com/ljharb/qs/issues?milestone=36&state=closed)
9
+ - [New] pass Buffers to the encoder/decoder directly (#161)
10
+ - [New] add "encoder" and "decoder" options, for custom param encoding/decoding (#160)
11
+ - [Fix] fix compacting of nested sparse arrays (#150)
12
+
13
+ ## [**6.1.0**](https://github.com/ljharb/qs/issues?milestone=35&state=closed)
2
14
  - [New] allowDots option for `stringify` (#151)
3
15
  - [Fix] "sort" option should work at a depth of 3 or more (#151)
4
16
  - [Fix] Restore `dist` directory; will be removed in v7 (#148)
@@ -12,6 +24,9 @@
12
24
  ## [**6.0.0**](https://github.com/ljharb/qs/issues?milestone=31&state=closed)
13
25
  - [**#124**](https://github.com/ljharb/qs/issues/124) Use ES6 and drop support for node < v4
14
26
 
27
+ ## **5.2.1**
28
+ - [Fix] ensure `key[]=x&key[]&key[]=y` results in 3, not 2, values
29
+
15
30
  ## [**5.2.0**](https://github.com/ljharb/qs/issues?milestone=30&state=closed)
16
31
  - [**#64**](https://github.com/ljharb/qs/issues/64) Add option to sort object keys in the query string
17
32
 
package/README.md CHANGED
@@ -225,6 +225,26 @@ var unencoded = qs.stringify({ a: { b: 'c' } }, { encode: false });
225
225
  assert.equal(unencoded, 'a[b]=c');
226
226
  ```
227
227
 
228
+ This encoding can also be replaced by a custom encoding method set as `encoder` option:
229
+
230
+ ```javascript
231
+ var encoded = qs.stringify({ a: { b: 'c' } }, { encoder: function (str) {
232
+ // Passed in values `a`, `b`, `c`
233
+ return // Return encoded string
234
+ }})
235
+ ```
236
+
237
+ _(Note: the `encoder` option does not apply if `encode` is `false`)_
238
+
239
+ Analogue to the `encoder` there is a `decoder` option for `parse` to override decoding of properties and values:
240
+
241
+ ```javascript
242
+ var decoded = qs.parse('x=z', { decoder: function (str) {
243
+ // Passed in values `x`, `z`
244
+ return // Return decoded string
245
+ }})
246
+ ```
247
+
228
248
  Examples beyond this point will be shown as though the output is not URI encoded for clarity. Please note that the return values in these cases *will* be URI encoded during real usage.
229
249
 
230
250
  When arrays are stringified, by default they are given explicit indices:
@@ -333,3 +353,24 @@ To completely skip rendering keys with `null` values, use the `skipNulls` flag:
333
353
  var nullsSkipped = qs.stringify({ a: 'b', c: null}, { skipNulls: true });
334
354
  assert.equal(nullsSkipped, 'a=b');
335
355
  ```
356
+
357
+ ### Dealing with special character sets
358
+
359
+ By default the encoding and decoding of characters is done in `utf-8`. If you
360
+ wish to encode querystrings to a different character set (i.e.
361
+ [Shift JIS](https://en.wikipedia.org/wiki/Shift_JIS)) you can use the
362
+ [`qs-iconv`](https://github.com/martinheidegger/qs-iconv) library:
363
+
364
+ ```javascript
365
+ var encoder = require('qs-iconv/encoder')('shift_jis');
366
+ var shiftJISEncoded = qs.stringify({ a: 'こんにちは!' }, { encoder: encoder });
367
+ assert.equal(shiftJISEncoded, 'a=%82%B1%82%F1%82%C9%82%BF%82%CD%81I');
368
+ ```
369
+
370
+ This also works for decoding of query strings:
371
+
372
+ ```javascript
373
+ var decoder = require('qs-iconv/decoder')('shift_jis');
374
+ var obj = qs.parse('a=%82%B1%82%F1%82%C9%82%BF%82%CD%81I', { decoder: decoder });
375
+ assert.deepEqual(obj, { a: 'こんにちは!' });
376
+ ```
package/dist/qs.js CHANGED
@@ -14,7 +14,9 @@ module.exports = {
14
14
 
15
15
  var Utils = require('./utils');
16
16
 
17
- var internals = {
17
+ var has = Object.prototype.hasOwnProperty;
18
+
19
+ var defaults = {
18
20
  delimiter: '&',
19
21
  depth: 5,
20
22
  arrayLimit: 20,
@@ -22,10 +24,11 @@ var internals = {
22
24
  strictNullHandling: false,
23
25
  plainObjects: false,
24
26
  allowPrototypes: false,
25
- allowDots: false
27
+ allowDots: false,
28
+ decoder: Utils.decode
26
29
  };
27
30
 
28
- internals.parseValues = function (str, options) {
31
+ var parseValues = function parseValues(str, options) {
29
32
  var obj = {};
30
33
  var parts = str.split(options.delimiter, options.parameterLimit === Infinity ? undefined : options.parameterLimit);
31
34
 
@@ -33,28 +36,25 @@ internals.parseValues = function (str, options) {
33
36
  var part = parts[i];
34
37
  var pos = part.indexOf(']=') === -1 ? part.indexOf('=') : part.indexOf(']=') + 1;
35
38
 
39
+ var key, val;
36
40
  if (pos === -1) {
37
- obj[Utils.decode(part)] = '';
38
-
39
- if (options.strictNullHandling) {
40
- obj[Utils.decode(part)] = null;
41
- }
41
+ key = options.decoder(part);
42
+ val = options.strictNullHandling ? null : '';
42
43
  } else {
43
- var key = Utils.decode(part.slice(0, pos));
44
- var val = Utils.decode(part.slice(pos + 1));
45
-
46
- if (Object.prototype.hasOwnProperty.call(obj, key)) {
47
- obj[key] = [].concat(obj[key]).concat(val);
48
- } else {
49
- obj[key] = val;
50
- }
44
+ key = options.decoder(part.slice(0, pos));
45
+ val = options.decoder(part.slice(pos + 1));
46
+ }
47
+ if (has.call(obj, key)) {
48
+ obj[key] = [].concat(obj[key]).concat(val);
49
+ } else {
50
+ obj[key] = val;
51
51
  }
52
52
  }
53
53
 
54
54
  return obj;
55
55
  };
56
56
 
57
- internals.parseObject = function (chain, val, options) {
57
+ var parseObject = function parseObject(chain, val, options) {
58
58
  if (!chain.length) {
59
59
  return val;
60
60
  }
@@ -64,7 +64,7 @@ internals.parseObject = function (chain, val, options) {
64
64
  var obj;
65
65
  if (root === '[]') {
66
66
  obj = [];
67
- obj = obj.concat(internals.parseObject(chain, val, options));
67
+ obj = obj.concat(parseObject(chain, val, options));
68
68
  } else {
69
69
  obj = options.plainObjects ? Object.create(null) : {};
70
70
  var cleanRoot = root[0] === '[' && root[root.length - 1] === ']' ? root.slice(1, root.length - 1) : root;
@@ -77,16 +77,16 @@ internals.parseObject = function (chain, val, options) {
77
77
  (options.parseArrays && index <= options.arrayLimit)
78
78
  ) {
79
79
  obj = [];
80
- obj[index] = internals.parseObject(chain, val, options);
80
+ obj[index] = parseObject(chain, val, options);
81
81
  } else {
82
- obj[cleanRoot] = internals.parseObject(chain, val, options);
82
+ obj[cleanRoot] = parseObject(chain, val, options);
83
83
  }
84
84
  }
85
85
 
86
86
  return obj;
87
87
  };
88
88
 
89
- internals.parseKeys = function (givenKey, val, options) {
89
+ var parseKeys = function parseKeys(givenKey, val, options) {
90
90
  if (!givenKey) {
91
91
  return;
92
92
  }
@@ -109,7 +109,7 @@ internals.parseKeys = function (givenKey, val, options) {
109
109
  if (segment[1]) {
110
110
  // If we aren't using plain objects, optionally prefix keys
111
111
  // that would overwrite object prototype properties
112
- if (!options.plainObjects && Object.prototype.hasOwnProperty(segment[1])) {
112
+ if (!options.plainObjects && has.call(Object.prototype, segment[1])) {
113
113
  if (!options.allowPrototypes) {
114
114
  return;
115
115
  }
@@ -123,7 +123,7 @@ internals.parseKeys = function (givenKey, val, options) {
123
123
  var i = 0;
124
124
  while ((segment = child.exec(key)) !== null && i < options.depth) {
125
125
  i += 1;
126
- if (!options.plainObjects && Object.prototype.hasOwnProperty(segment[1].replace(/\[|\]/g, ''))) {
126
+ if (!options.plainObjects && has.call(Object.prototype, segment[1].replace(/\[|\]/g, ''))) {
127
127
  if (!options.allowPrototypes) {
128
128
  continue;
129
129
  }
@@ -137,30 +137,32 @@ internals.parseKeys = function (givenKey, val, options) {
137
137
  keys.push('[' + key.slice(segment.index) + ']');
138
138
  }
139
139
 
140
- return internals.parseObject(keys, val, options);
140
+ return parseObject(keys, val, options);
141
141
  };
142
142
 
143
143
  module.exports = function (str, opts) {
144
144
  var options = opts || {};
145
- options.delimiter = typeof options.delimiter === 'string' || Utils.isRegExp(options.delimiter) ? options.delimiter : internals.delimiter;
146
- options.depth = typeof options.depth === 'number' ? options.depth : internals.depth;
147
- options.arrayLimit = typeof options.arrayLimit === 'number' ? options.arrayLimit : internals.arrayLimit;
145
+
146
+ if (options.decoder !== null && options.decoder !== undefined && typeof options.decoder !== 'function') {
147
+ throw new TypeError('Decoder has to be a function.');
148
+ }
149
+
150
+ options.delimiter = typeof options.delimiter === 'string' || Utils.isRegExp(options.delimiter) ? options.delimiter : defaults.delimiter;
151
+ options.depth = typeof options.depth === 'number' ? options.depth : defaults.depth;
152
+ options.arrayLimit = typeof options.arrayLimit === 'number' ? options.arrayLimit : defaults.arrayLimit;
148
153
  options.parseArrays = options.parseArrays !== false;
149
- options.allowDots = typeof options.allowDots === 'boolean' ? options.allowDots : internals.allowDots;
150
- options.plainObjects = typeof options.plainObjects === 'boolean' ? options.plainObjects : internals.plainObjects;
151
- options.allowPrototypes = typeof options.allowPrototypes === 'boolean' ? options.allowPrototypes : internals.allowPrototypes;
152
- options.parameterLimit = typeof options.parameterLimit === 'number' ? options.parameterLimit : internals.parameterLimit;
153
- options.strictNullHandling = typeof options.strictNullHandling === 'boolean' ? options.strictNullHandling : internals.strictNullHandling;
154
-
155
- if (
156
- str === '' ||
157
- str === null ||
158
- typeof str === 'undefined'
159
- ) {
154
+ options.decoder = typeof options.decoder === 'function' ? options.decoder : defaults.decoder;
155
+ options.allowDots = typeof options.allowDots === 'boolean' ? options.allowDots : defaults.allowDots;
156
+ options.plainObjects = typeof options.plainObjects === 'boolean' ? options.plainObjects : defaults.plainObjects;
157
+ options.allowPrototypes = typeof options.allowPrototypes === 'boolean' ? options.allowPrototypes : defaults.allowPrototypes;
158
+ options.parameterLimit = typeof options.parameterLimit === 'number' ? options.parameterLimit : defaults.parameterLimit;
159
+ options.strictNullHandling = typeof options.strictNullHandling === 'boolean' ? options.strictNullHandling : defaults.strictNullHandling;
160
+
161
+ if (str === '' || str === null || typeof str === 'undefined') {
160
162
  return options.plainObjects ? Object.create(null) : {};
161
163
  }
162
164
 
163
- var tempObj = typeof str === 'string' ? internals.parseValues(str, options) : str;
165
+ var tempObj = typeof str === 'string' ? parseValues(str, options) : str;
164
166
  var obj = options.plainObjects ? Object.create(null) : {};
165
167
 
166
168
  // Iterate over the keys and setup the new object
@@ -168,7 +170,7 @@ module.exports = function (str, opts) {
168
170
  var keys = Object.keys(tempObj);
169
171
  for (var i = 0; i < keys.length; ++i) {
170
172
  var key = keys[i];
171
- var newObj = internals.parseKeys(key, tempObj[key], options);
173
+ var newObj = parseKeys(key, tempObj[key], options);
172
174
  obj = Utils.merge(obj, newObj, options);
173
175
  }
174
176
 
@@ -180,45 +182,45 @@ module.exports = function (str, opts) {
180
182
 
181
183
  var Utils = require('./utils');
182
184
 
183
- var internals = {
184
- delimiter: '&',
185
- arrayPrefixGenerators: {
186
- brackets: function (prefix) {
187
- return prefix + '[]';
188
- },
189
- indices: function (prefix, key) {
190
- return prefix + '[' + key + ']';
191
- },
192
- repeat: function (prefix) {
193
- return prefix;
194
- }
185
+ var arrayPrefixGenerators = {
186
+ brackets: function brackets(prefix) {
187
+ return prefix + '[]';
188
+ },
189
+ indices: function indices(prefix, key) {
190
+ return prefix + '[' + key + ']';
195
191
  },
192
+ repeat: function repeat(prefix) {
193
+ return prefix;
194
+ }
195
+ };
196
+
197
+ var defaults = {
198
+ delimiter: '&',
196
199
  strictNullHandling: false,
197
200
  skipNulls: false,
198
- encode: true
201
+ encode: true,
202
+ encoder: Utils.encode
199
203
  };
200
204
 
201
- internals.stringify = function (object, prefix, generateArrayPrefix, strictNullHandling, skipNulls, encode, filter, sort, allowDots) {
205
+ var stringify = function stringify(object, prefix, generateArrayPrefix, strictNullHandling, skipNulls, encoder, filter, sort, allowDots) {
202
206
  var obj = object;
203
207
  if (typeof filter === 'function') {
204
208
  obj = filter(prefix, obj);
205
- } else if (Utils.isBuffer(obj)) {
206
- obj = String(obj);
207
209
  } else if (obj instanceof Date) {
208
210
  obj = obj.toISOString();
209
211
  } else if (obj === null) {
210
212
  if (strictNullHandling) {
211
- return encode ? Utils.encode(prefix) : prefix;
213
+ return encoder ? encoder(prefix) : prefix;
212
214
  }
213
215
 
214
216
  obj = '';
215
217
  }
216
218
 
217
- if (typeof obj === 'string' || typeof obj === 'number' || typeof obj === 'boolean') {
218
- if (encode) {
219
- return [Utils.encode(prefix) + '=' + Utils.encode(obj)];
219
+ if (typeof obj === 'string' || typeof obj === 'number' || typeof obj === 'boolean' || Utils.isBuffer(obj)) {
220
+ if (encoder) {
221
+ return [encoder(prefix) + '=' + encoder(obj)];
220
222
  }
221
- return [prefix + '=' + obj];
223
+ return [prefix + '=' + String(obj)];
222
224
  }
223
225
 
224
226
  var values = [];
@@ -243,9 +245,9 @@ internals.stringify = function (object, prefix, generateArrayPrefix, strictNullH
243
245
  }
244
246
 
245
247
  if (Array.isArray(obj)) {
246
- values = values.concat(internals.stringify(obj[key], generateArrayPrefix(prefix, key), generateArrayPrefix, strictNullHandling, skipNulls, encode, filter, sort, allowDots));
248
+ values = values.concat(stringify(obj[key], generateArrayPrefix(prefix, key), generateArrayPrefix, strictNullHandling, skipNulls, encoder, filter, sort, allowDots));
247
249
  } else {
248
- values = values.concat(internals.stringify(obj[key], prefix + (allowDots ? '.' + key : '[' + key + ']'), generateArrayPrefix, strictNullHandling, skipNulls, encode, filter, sort, allowDots));
250
+ values = values.concat(stringify(obj[key], prefix + (allowDots ? '.' + key : '[' + key + ']'), generateArrayPrefix, strictNullHandling, skipNulls, encoder, filter, sort, allowDots));
249
251
  }
250
252
  }
251
253
 
@@ -255,14 +257,20 @@ internals.stringify = function (object, prefix, generateArrayPrefix, strictNullH
255
257
  module.exports = function (object, opts) {
256
258
  var obj = object;
257
259
  var options = opts || {};
258
- var delimiter = typeof options.delimiter === 'undefined' ? internals.delimiter : options.delimiter;
259
- var strictNullHandling = typeof options.strictNullHandling === 'boolean' ? options.strictNullHandling : internals.strictNullHandling;
260
- var skipNulls = typeof options.skipNulls === 'boolean' ? options.skipNulls : internals.skipNulls;
261
- var encode = typeof options.encode === 'boolean' ? options.encode : internals.encode;
260
+ var delimiter = typeof options.delimiter === 'undefined' ? defaults.delimiter : options.delimiter;
261
+ var strictNullHandling = typeof options.strictNullHandling === 'boolean' ? options.strictNullHandling : defaults.strictNullHandling;
262
+ var skipNulls = typeof options.skipNulls === 'boolean' ? options.skipNulls : defaults.skipNulls;
263
+ var encode = typeof options.encode === 'boolean' ? options.encode : defaults.encode;
264
+ var encoder = encode ? (typeof options.encoder === 'function' ? options.encoder : defaults.encoder) : null;
262
265
  var sort = typeof options.sort === 'function' ? options.sort : null;
263
266
  var allowDots = typeof options.allowDots === 'undefined' ? false : options.allowDots;
264
267
  var objKeys;
265
268
  var filter;
269
+
270
+ if (options.encoder !== null && options.encoder !== undefined && typeof options.encoder !== 'function') {
271
+ throw new TypeError('Encoder has to be a function.');
272
+ }
273
+
266
274
  if (typeof options.filter === 'function') {
267
275
  filter = options.filter;
268
276
  obj = filter('', obj);
@@ -277,7 +285,7 @@ module.exports = function (object, opts) {
277
285
  }
278
286
 
279
287
  var arrayFormat;
280
- if (options.arrayFormat in internals.arrayPrefixGenerators) {
288
+ if (options.arrayFormat in arrayPrefixGenerators) {
281
289
  arrayFormat = options.arrayFormat;
282
290
  } else if ('indices' in options) {
283
291
  arrayFormat = options.indices ? 'indices' : 'repeat';
@@ -285,7 +293,7 @@ module.exports = function (object, opts) {
285
293
  arrayFormat = 'indices';
286
294
  }
287
295
 
288
- var generateArrayPrefix = internals.arrayPrefixGenerators[arrayFormat];
296
+ var generateArrayPrefix = arrayPrefixGenerators[arrayFormat];
289
297
 
290
298
  if (!objKeys) {
291
299
  objKeys = Object.keys(obj);
@@ -302,7 +310,7 @@ module.exports = function (object, opts) {
302
310
  continue;
303
311
  }
304
312
 
305
- keys = keys.concat(internals.stringify(obj[key], key, generateArrayPrefix, strictNullHandling, skipNulls, encode, filter, sort, allowDots));
313
+ keys = keys.concat(stringify(obj[key], key, generateArrayPrefix, strictNullHandling, skipNulls, encoder, filter, sort, allowDots));
306
314
  }
307
315
 
308
316
  return keys.join(delimiter);
@@ -357,7 +365,7 @@ exports.merge = function (target, source, options) {
357
365
  mergeTarget = exports.arrayToObject(target, options);
358
366
  }
359
367
 
360
- return Object.keys(source).reduce(function (acc, key) {
368
+ return Object.keys(source).reduce(function (acc, key) {
361
369
  var value = source[key];
362
370
 
363
371
  if (Object.prototype.hasOwnProperty.call(acc, key)) {
@@ -365,7 +373,7 @@ exports.merge = function (target, source, options) {
365
373
  } else {
366
374
  acc[key] = value;
367
375
  }
368
- return acc;
376
+ return acc;
369
377
  }, mergeTarget);
370
378
  };
371
379
 
@@ -420,7 +428,7 @@ exports.encode = function (str) {
420
428
 
421
429
  i += 1;
422
430
  c = 0x10000 + (((c & 0x3FF) << 10) | (string.charCodeAt(i) & 0x3FF));
423
- out += (hexTable[0xF0 | (c >> 18)] + hexTable[0x80 | ((c >> 12) & 0x3F)] + hexTable[0x80 | ((c >> 6) & 0x3F)] + hexTable[0x80 | (c & 0x3F)]);
431
+ out += hexTable[0xF0 | (c >> 18)] + hexTable[0x80 | ((c >> 12) & 0x3F)] + hexTable[0x80 | ((c >> 6) & 0x3F)] + hexTable[0x80 | (c & 0x3F)];
424
432
  }
425
433
 
426
434
  return out;
@@ -443,7 +451,9 @@ exports.compact = function (obj, references) {
443
451
  var compacted = [];
444
452
 
445
453
  for (var i = 0; i < obj.length; ++i) {
446
- if (typeof obj[i] !== 'undefined') {
454
+ if (obj[i] && typeof obj[i] === 'object') {
455
+ compacted.push(exports.compact(obj[i], refs));
456
+ } else if (typeof obj[i] !== 'undefined') {
447
457
  compacted.push(obj[i]);
448
458
  }
449
459
  }
package/lib/parse.js CHANGED
@@ -2,7 +2,9 @@
2
2
 
3
3
  var Utils = require('./utils');
4
4
 
5
- var internals = {
5
+ var has = Object.prototype.hasOwnProperty;
6
+
7
+ var defaults = {
6
8
  delimiter: '&',
7
9
  depth: 5,
8
10
  arrayLimit: 20,
@@ -10,10 +12,11 @@ var internals = {
10
12
  strictNullHandling: false,
11
13
  plainObjects: false,
12
14
  allowPrototypes: false,
13
- allowDots: false
15
+ allowDots: false,
16
+ decoder: Utils.decode
14
17
  };
15
18
 
16
- internals.parseValues = function (str, options) {
19
+ var parseValues = function parseValues(str, options) {
17
20
  var obj = {};
18
21
  var parts = str.split(options.delimiter, options.parameterLimit === Infinity ? undefined : options.parameterLimit);
19
22
 
@@ -21,28 +24,25 @@ internals.parseValues = function (str, options) {
21
24
  var part = parts[i];
22
25
  var pos = part.indexOf(']=') === -1 ? part.indexOf('=') : part.indexOf(']=') + 1;
23
26
 
27
+ var key, val;
24
28
  if (pos === -1) {
25
- obj[Utils.decode(part)] = '';
26
-
27
- if (options.strictNullHandling) {
28
- obj[Utils.decode(part)] = null;
29
- }
29
+ key = options.decoder(part);
30
+ val = options.strictNullHandling ? null : '';
30
31
  } else {
31
- var key = Utils.decode(part.slice(0, pos));
32
- var val = Utils.decode(part.slice(pos + 1));
33
-
34
- if (Object.prototype.hasOwnProperty.call(obj, key)) {
35
- obj[key] = [].concat(obj[key]).concat(val);
36
- } else {
37
- obj[key] = val;
38
- }
32
+ key = options.decoder(part.slice(0, pos));
33
+ val = options.decoder(part.slice(pos + 1));
34
+ }
35
+ if (has.call(obj, key)) {
36
+ obj[key] = [].concat(obj[key]).concat(val);
37
+ } else {
38
+ obj[key] = val;
39
39
  }
40
40
  }
41
41
 
42
42
  return obj;
43
43
  };
44
44
 
45
- internals.parseObject = function (chain, val, options) {
45
+ var parseObject = function parseObject(chain, val, options) {
46
46
  if (!chain.length) {
47
47
  return val;
48
48
  }
@@ -52,7 +52,7 @@ internals.parseObject = function (chain, val, options) {
52
52
  var obj;
53
53
  if (root === '[]') {
54
54
  obj = [];
55
- obj = obj.concat(internals.parseObject(chain, val, options));
55
+ obj = obj.concat(parseObject(chain, val, options));
56
56
  } else {
57
57
  obj = options.plainObjects ? Object.create(null) : {};
58
58
  var cleanRoot = root[0] === '[' && root[root.length - 1] === ']' ? root.slice(1, root.length - 1) : root;
@@ -65,16 +65,16 @@ internals.parseObject = function (chain, val, options) {
65
65
  (options.parseArrays && index <= options.arrayLimit)
66
66
  ) {
67
67
  obj = [];
68
- obj[index] = internals.parseObject(chain, val, options);
68
+ obj[index] = parseObject(chain, val, options);
69
69
  } else {
70
- obj[cleanRoot] = internals.parseObject(chain, val, options);
70
+ obj[cleanRoot] = parseObject(chain, val, options);
71
71
  }
72
72
  }
73
73
 
74
74
  return obj;
75
75
  };
76
76
 
77
- internals.parseKeys = function (givenKey, val, options) {
77
+ var parseKeys = function parseKeys(givenKey, val, options) {
78
78
  if (!givenKey) {
79
79
  return;
80
80
  }
@@ -97,7 +97,7 @@ internals.parseKeys = function (givenKey, val, options) {
97
97
  if (segment[1]) {
98
98
  // If we aren't using plain objects, optionally prefix keys
99
99
  // that would overwrite object prototype properties
100
- if (!options.plainObjects && Object.prototype.hasOwnProperty(segment[1])) {
100
+ if (!options.plainObjects && has.call(Object.prototype, segment[1])) {
101
101
  if (!options.allowPrototypes) {
102
102
  return;
103
103
  }
@@ -111,7 +111,7 @@ internals.parseKeys = function (givenKey, val, options) {
111
111
  var i = 0;
112
112
  while ((segment = child.exec(key)) !== null && i < options.depth) {
113
113
  i += 1;
114
- if (!options.plainObjects && Object.prototype.hasOwnProperty(segment[1].replace(/\[|\]/g, ''))) {
114
+ if (!options.plainObjects && has.call(Object.prototype, segment[1].replace(/\[|\]/g, ''))) {
115
115
  if (!options.allowPrototypes) {
116
116
  continue;
117
117
  }
@@ -125,30 +125,32 @@ internals.parseKeys = function (givenKey, val, options) {
125
125
  keys.push('[' + key.slice(segment.index) + ']');
126
126
  }
127
127
 
128
- return internals.parseObject(keys, val, options);
128
+ return parseObject(keys, val, options);
129
129
  };
130
130
 
131
131
  module.exports = function (str, opts) {
132
132
  var options = opts || {};
133
- options.delimiter = typeof options.delimiter === 'string' || Utils.isRegExp(options.delimiter) ? options.delimiter : internals.delimiter;
134
- options.depth = typeof options.depth === 'number' ? options.depth : internals.depth;
135
- options.arrayLimit = typeof options.arrayLimit === 'number' ? options.arrayLimit : internals.arrayLimit;
133
+
134
+ if (options.decoder !== null && options.decoder !== undefined && typeof options.decoder !== 'function') {
135
+ throw new TypeError('Decoder has to be a function.');
136
+ }
137
+
138
+ options.delimiter = typeof options.delimiter === 'string' || Utils.isRegExp(options.delimiter) ? options.delimiter : defaults.delimiter;
139
+ options.depth = typeof options.depth === 'number' ? options.depth : defaults.depth;
140
+ options.arrayLimit = typeof options.arrayLimit === 'number' ? options.arrayLimit : defaults.arrayLimit;
136
141
  options.parseArrays = options.parseArrays !== false;
137
- options.allowDots = typeof options.allowDots === 'boolean' ? options.allowDots : internals.allowDots;
138
- options.plainObjects = typeof options.plainObjects === 'boolean' ? options.plainObjects : internals.plainObjects;
139
- options.allowPrototypes = typeof options.allowPrototypes === 'boolean' ? options.allowPrototypes : internals.allowPrototypes;
140
- options.parameterLimit = typeof options.parameterLimit === 'number' ? options.parameterLimit : internals.parameterLimit;
141
- options.strictNullHandling = typeof options.strictNullHandling === 'boolean' ? options.strictNullHandling : internals.strictNullHandling;
142
-
143
- if (
144
- str === '' ||
145
- str === null ||
146
- typeof str === 'undefined'
147
- ) {
142
+ options.decoder = typeof options.decoder === 'function' ? options.decoder : defaults.decoder;
143
+ options.allowDots = typeof options.allowDots === 'boolean' ? options.allowDots : defaults.allowDots;
144
+ options.plainObjects = typeof options.plainObjects === 'boolean' ? options.plainObjects : defaults.plainObjects;
145
+ options.allowPrototypes = typeof options.allowPrototypes === 'boolean' ? options.allowPrototypes : defaults.allowPrototypes;
146
+ options.parameterLimit = typeof options.parameterLimit === 'number' ? options.parameterLimit : defaults.parameterLimit;
147
+ options.strictNullHandling = typeof options.strictNullHandling === 'boolean' ? options.strictNullHandling : defaults.strictNullHandling;
148
+
149
+ if (str === '' || str === null || typeof str === 'undefined') {
148
150
  return options.plainObjects ? Object.create(null) : {};
149
151
  }
150
152
 
151
- var tempObj = typeof str === 'string' ? internals.parseValues(str, options) : str;
153
+ var tempObj = typeof str === 'string' ? parseValues(str, options) : str;
152
154
  var obj = options.plainObjects ? Object.create(null) : {};
153
155
 
154
156
  // Iterate over the keys and setup the new object
@@ -156,7 +158,7 @@ module.exports = function (str, opts) {
156
158
  var keys = Object.keys(tempObj);
157
159
  for (var i = 0; i < keys.length; ++i) {
158
160
  var key = keys[i];
159
- var newObj = internals.parseKeys(key, tempObj[key], options);
161
+ var newObj = parseKeys(key, tempObj[key], options);
160
162
  obj = Utils.merge(obj, newObj, options);
161
163
  }
162
164
 
package/lib/stringify.js CHANGED
@@ -2,45 +2,45 @@
2
2
 
3
3
  var Utils = require('./utils');
4
4
 
5
- var internals = {
6
- delimiter: '&',
7
- arrayPrefixGenerators: {
8
- brackets: function (prefix) {
9
- return prefix + '[]';
10
- },
11
- indices: function (prefix, key) {
12
- return prefix + '[' + key + ']';
13
- },
14
- repeat: function (prefix) {
15
- return prefix;
16
- }
5
+ var arrayPrefixGenerators = {
6
+ brackets: function brackets(prefix) {
7
+ return prefix + '[]';
8
+ },
9
+ indices: function indices(prefix, key) {
10
+ return prefix + '[' + key + ']';
17
11
  },
12
+ repeat: function repeat(prefix) {
13
+ return prefix;
14
+ }
15
+ };
16
+
17
+ var defaults = {
18
+ delimiter: '&',
18
19
  strictNullHandling: false,
19
20
  skipNulls: false,
20
- encode: true
21
+ encode: true,
22
+ encoder: Utils.encode
21
23
  };
22
24
 
23
- internals.stringify = function (object, prefix, generateArrayPrefix, strictNullHandling, skipNulls, encode, filter, sort, allowDots) {
25
+ var stringify = function stringify(object, prefix, generateArrayPrefix, strictNullHandling, skipNulls, encoder, filter, sort, allowDots) {
24
26
  var obj = object;
25
27
  if (typeof filter === 'function') {
26
28
  obj = filter(prefix, obj);
27
- } else if (Utils.isBuffer(obj)) {
28
- obj = String(obj);
29
29
  } else if (obj instanceof Date) {
30
30
  obj = obj.toISOString();
31
31
  } else if (obj === null) {
32
32
  if (strictNullHandling) {
33
- return encode ? Utils.encode(prefix) : prefix;
33
+ return encoder ? encoder(prefix) : prefix;
34
34
  }
35
35
 
36
36
  obj = '';
37
37
  }
38
38
 
39
- if (typeof obj === 'string' || typeof obj === 'number' || typeof obj === 'boolean') {
40
- if (encode) {
41
- return [Utils.encode(prefix) + '=' + Utils.encode(obj)];
39
+ if (typeof obj === 'string' || typeof obj === 'number' || typeof obj === 'boolean' || Utils.isBuffer(obj)) {
40
+ if (encoder) {
41
+ return [encoder(prefix) + '=' + encoder(obj)];
42
42
  }
43
- return [prefix + '=' + obj];
43
+ return [prefix + '=' + String(obj)];
44
44
  }
45
45
 
46
46
  var values = [];
@@ -65,9 +65,9 @@ internals.stringify = function (object, prefix, generateArrayPrefix, strictNullH
65
65
  }
66
66
 
67
67
  if (Array.isArray(obj)) {
68
- values = values.concat(internals.stringify(obj[key], generateArrayPrefix(prefix, key), generateArrayPrefix, strictNullHandling, skipNulls, encode, filter, sort, allowDots));
68
+ values = values.concat(stringify(obj[key], generateArrayPrefix(prefix, key), generateArrayPrefix, strictNullHandling, skipNulls, encoder, filter, sort, allowDots));
69
69
  } else {
70
- values = values.concat(internals.stringify(obj[key], prefix + (allowDots ? '.' + key : '[' + key + ']'), generateArrayPrefix, strictNullHandling, skipNulls, encode, filter, sort, allowDots));
70
+ values = values.concat(stringify(obj[key], prefix + (allowDots ? '.' + key : '[' + key + ']'), generateArrayPrefix, strictNullHandling, skipNulls, encoder, filter, sort, allowDots));
71
71
  }
72
72
  }
73
73
 
@@ -77,14 +77,20 @@ internals.stringify = function (object, prefix, generateArrayPrefix, strictNullH
77
77
  module.exports = function (object, opts) {
78
78
  var obj = object;
79
79
  var options = opts || {};
80
- var delimiter = typeof options.delimiter === 'undefined' ? internals.delimiter : options.delimiter;
81
- var strictNullHandling = typeof options.strictNullHandling === 'boolean' ? options.strictNullHandling : internals.strictNullHandling;
82
- var skipNulls = typeof options.skipNulls === 'boolean' ? options.skipNulls : internals.skipNulls;
83
- var encode = typeof options.encode === 'boolean' ? options.encode : internals.encode;
80
+ var delimiter = typeof options.delimiter === 'undefined' ? defaults.delimiter : options.delimiter;
81
+ var strictNullHandling = typeof options.strictNullHandling === 'boolean' ? options.strictNullHandling : defaults.strictNullHandling;
82
+ var skipNulls = typeof options.skipNulls === 'boolean' ? options.skipNulls : defaults.skipNulls;
83
+ var encode = typeof options.encode === 'boolean' ? options.encode : defaults.encode;
84
+ var encoder = encode ? (typeof options.encoder === 'function' ? options.encoder : defaults.encoder) : null;
84
85
  var sort = typeof options.sort === 'function' ? options.sort : null;
85
86
  var allowDots = typeof options.allowDots === 'undefined' ? false : options.allowDots;
86
87
  var objKeys;
87
88
  var filter;
89
+
90
+ if (options.encoder !== null && options.encoder !== undefined && typeof options.encoder !== 'function') {
91
+ throw new TypeError('Encoder has to be a function.');
92
+ }
93
+
88
94
  if (typeof options.filter === 'function') {
89
95
  filter = options.filter;
90
96
  obj = filter('', obj);
@@ -99,7 +105,7 @@ module.exports = function (object, opts) {
99
105
  }
100
106
 
101
107
  var arrayFormat;
102
- if (options.arrayFormat in internals.arrayPrefixGenerators) {
108
+ if (options.arrayFormat in arrayPrefixGenerators) {
103
109
  arrayFormat = options.arrayFormat;
104
110
  } else if ('indices' in options) {
105
111
  arrayFormat = options.indices ? 'indices' : 'repeat';
@@ -107,7 +113,7 @@ module.exports = function (object, opts) {
107
113
  arrayFormat = 'indices';
108
114
  }
109
115
 
110
- var generateArrayPrefix = internals.arrayPrefixGenerators[arrayFormat];
116
+ var generateArrayPrefix = arrayPrefixGenerators[arrayFormat];
111
117
 
112
118
  if (!objKeys) {
113
119
  objKeys = Object.keys(obj);
@@ -124,7 +130,7 @@ module.exports = function (object, opts) {
124
130
  continue;
125
131
  }
126
132
 
127
- keys = keys.concat(internals.stringify(obj[key], key, generateArrayPrefix, strictNullHandling, skipNulls, encode, filter, sort, allowDots));
133
+ keys = keys.concat(stringify(obj[key], key, generateArrayPrefix, strictNullHandling, skipNulls, encoder, filter, sort, allowDots));
128
134
  }
129
135
 
130
136
  return keys.join(delimiter);
package/lib/utils.js CHANGED
@@ -46,7 +46,7 @@ exports.merge = function (target, source, options) {
46
46
  mergeTarget = exports.arrayToObject(target, options);
47
47
  }
48
48
 
49
- return Object.keys(source).reduce(function (acc, key) {
49
+ return Object.keys(source).reduce(function (acc, key) {
50
50
  var value = source[key];
51
51
 
52
52
  if (Object.prototype.hasOwnProperty.call(acc, key)) {
@@ -54,7 +54,7 @@ exports.merge = function (target, source, options) {
54
54
  } else {
55
55
  acc[key] = value;
56
56
  }
57
- return acc;
57
+ return acc;
58
58
  }, mergeTarget);
59
59
  };
60
60
 
@@ -109,7 +109,7 @@ exports.encode = function (str) {
109
109
 
110
110
  i += 1;
111
111
  c = 0x10000 + (((c & 0x3FF) << 10) | (string.charCodeAt(i) & 0x3FF));
112
- out += (hexTable[0xF0 | (c >> 18)] + hexTable[0x80 | ((c >> 12) & 0x3F)] + hexTable[0x80 | ((c >> 6) & 0x3F)] + hexTable[0x80 | (c & 0x3F)]);
112
+ out += hexTable[0xF0 | (c >> 18)] + hexTable[0x80 | ((c >> 12) & 0x3F)] + hexTable[0x80 | ((c >> 6) & 0x3F)] + hexTable[0x80 | (c & 0x3F)];
113
113
  }
114
114
 
115
115
  return out;
@@ -132,7 +132,9 @@ exports.compact = function (obj, references) {
132
132
  var compacted = [];
133
133
 
134
134
  for (var i = 0; i < obj.length; ++i) {
135
- if (typeof obj[i] !== 'undefined') {
135
+ if (obj[i] && typeof obj[i] === 'object') {
136
+ compacted.push(exports.compact(obj[i], refs));
137
+ } else if (typeof obj[i] !== 'undefined') {
136
138
  compacted.push(obj[i]);
137
139
  }
138
140
  }
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "qs",
3
3
  "description": "A querystring parser that supports nesting and arrays, with a depth limit",
4
4
  "homepage": "https://github.com/ljharb/qs",
5
- "version": "6.1.0",
5
+ "version": "6.2.1",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "https://github.com/ljharb/qs.git"
@@ -24,17 +24,20 @@
24
24
  },
25
25
  "dependencies": {},
26
26
  "devDependencies": {
27
- "browserify": "^12.0.1",
28
- "tape": "^4.3.0",
27
+ "browserify": "^13.0.1",
28
+ "tape": "^4.6.0",
29
29
  "covert": "^1.1.0",
30
30
  "mkdirp": "^0.5.1",
31
- "eslint": "^1.10.3",
32
- "@ljharb/eslint-config": "^1.6.1",
31
+ "eslint": "^3.1.0",
32
+ "@ljharb/eslint-config": "^6.0.0",
33
33
  "parallelshell": "^2.0.0",
34
- "evalmd": "^0.0.16"
34
+ "iconv-lite": "^0.4.13",
35
+ "qs-iconv": "^1.0.3",
36
+ "evalmd": "^0.0.17"
35
37
  },
36
38
  "scripts": {
37
- "test": "parallelshell 'npm run readme' 'npm run lint' 'npm run coverage'",
39
+ "pretest": "npm run --silent readme && npm run --silent lint",
40
+ "test": "npm run --silent coverage",
38
41
  "tests-only": "node test",
39
42
  "readme": "evalmd README.md",
40
43
  "lint": "eslint lib/*.js text/*.js",
package/test/parse.js CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  var test = require('tape');
4
4
  var qs = require('../');
5
+ var iconv = require('iconv-lite');
5
6
 
6
7
  test('parse()', function (t) {
7
8
  t.test('parses a simple string', function (st) {
@@ -120,8 +121,11 @@ test('parse()', function (t) {
120
121
  st.deepEqual(qs.parse('foo[]=bar&foo[bad]=baz'), { foo: { '0': 'bar', bad: 'baz' } });
121
122
  st.deepEqual(qs.parse('foo[bad]=baz&foo[]=bar&foo[]=foo'), { foo: { bad: 'baz', '0': 'bar', '1': 'foo' } });
122
123
  st.deepEqual(qs.parse('foo[0][a]=a&foo[0][b]=b&foo[1][a]=aa&foo[1][b]=bb'), { foo: [{ a: 'a', b: 'b' }, { a: 'aa', b: 'bb' }] });
123
- st.deepEqual(qs.parse('a[]=b&a[t]=u&a[hasOwnProperty]=c'), { a: { '0': 'b', t: 'u', c: true } });
124
- st.deepEqual(qs.parse('a[]=b&a[hasOwnProperty]=c&a[x]=y'), { a: { '0': 'b', '1': 'c', x: 'y' } });
124
+
125
+ st.deepEqual(qs.parse('a[]=b&a[t]=u&a[hasOwnProperty]=c', { allowPrototypes: false }), { a: { '0': 'b', c: true, t: 'u' } });
126
+ st.deepEqual(qs.parse('a[]=b&a[t]=u&a[hasOwnProperty]=c', { allowPrototypes: true }), { a: { '0': 'b', t: 'u', hasOwnProperty: 'c' } });
127
+ st.deepEqual(qs.parse('a[]=b&a[hasOwnProperty]=c&a[x]=y', { allowPrototypes: false }), { a: { '0': 'b', '1': 'c', x: 'y' } });
128
+ st.deepEqual(qs.parse('a[]=b&a[hasOwnProperty]=c&a[x]=y', { allowPrototypes: true }), { a: { '0': 'b', hasOwnProperty: 'c', x: 'y' } });
125
129
  st.end();
126
130
  });
127
131
 
@@ -173,14 +177,42 @@ test('parse()', function (t) {
173
177
 
174
178
  t.test('allows for empty strings in arrays', function (st) {
175
179
  st.deepEqual(qs.parse('a[]=b&a[]=&a[]=c'), { a: ['b', '', 'c'] });
176
- st.deepEqual(qs.parse('a[0]=b&a[1]&a[2]=c&a[19]=', { strictNullHandling: true }), { a: ['b', null, 'c', ''] });
177
- st.deepEqual(qs.parse('a[0]=b&a[1]=&a[2]=c&a[19]', { strictNullHandling: true }), { a: ['b', '', 'c', null] });
178
- st.deepEqual(qs.parse('a[]=&a[]=b&a[]=c'), { a: ['', 'b', 'c'] });
180
+
181
+ st.deepEqual(
182
+ qs.parse('a[0]=b&a[1]&a[2]=c&a[19]=', { strictNullHandling: true, arrayLimit: 20 }),
183
+ { a: ['b', null, 'c', ''] },
184
+ 'with arrayLimit 20 + array indices: null then empty string works'
185
+ );
186
+ st.deepEqual(
187
+ qs.parse('a[]=b&a[]&a[]=c&a[]=', { strictNullHandling: true, arrayLimit: 0 }),
188
+ { a: ['b', null, 'c', ''] },
189
+ 'with arrayLimit 0 + array brackets: null then empty string works'
190
+ );
191
+
192
+ st.deepEqual(
193
+ qs.parse('a[0]=b&a[1]=&a[2]=c&a[19]', { strictNullHandling: true, arrayLimit: 20 }),
194
+ { a: ['b', '', 'c', null] },
195
+ 'with arrayLimit 20 + array indices: empty string then null works'
196
+ );
197
+ st.deepEqual(
198
+ qs.parse('a[]=b&a[]=&a[]=c&a[]', { strictNullHandling: true, arrayLimit: 0 }),
199
+ { a: ['b', '', 'c', null] },
200
+ 'with arrayLimit 0 + array brackets: empty string then null works'
201
+ );
202
+
203
+ st.deepEqual(
204
+ qs.parse('a[]=&a[]=b&a[]=c'),
205
+ { a: ['', 'b', 'c'] },
206
+ 'array brackets: empty strings work'
207
+ );
179
208
  st.end();
180
209
  });
181
210
 
182
211
  t.test('compacts sparse arrays', function (st) {
183
212
  st.deepEqual(qs.parse('a[10]=1&a[2]=2'), { a: ['2', '1'] });
213
+ st.deepEqual(qs.parse('a[1][b][2][c]=1'), { a: [{ b: [{ c: '1' }] }] });
214
+ st.deepEqual(qs.parse('a[1][2][3][c]=1'), { a: [[[{ c: '1' }]]] });
215
+ st.deepEqual(qs.parse('a[1][2][3][c][1]=1'), { a: [[[{ c: ['1'] }]]] });
184
216
  st.end();
185
217
  });
186
218
 
@@ -390,4 +422,30 @@ test('parse()', function (t) {
390
422
  st.deepEqual(qs.parse('a[]=b&a[c]=d', { plainObjects: true }), expectedArray);
391
423
  st.end();
392
424
  });
425
+
426
+ t.test('can parse with custom encoding', function (st) {
427
+ st.deepEqual(qs.parse('%8c%a7=%91%e5%8d%e3%95%7b', {
428
+ decoder: function (str) {
429
+ var reg = /\%([0-9A-F]{2})/ig;
430
+ var result = [];
431
+ var parts;
432
+ var last = 0;
433
+ while (parts = reg.exec(str)) {
434
+ result.push(parseInt(parts[1], 16));
435
+ last = parts.index + parts[0].length;
436
+ }
437
+ return iconv.decode(new Buffer(result), 'shift_jis').toString();
438
+ }
439
+ }), { 県: '大阪府' });
440
+ st.end();
441
+ });
442
+
443
+ t.test('throws error with wrong decoder', function (st) {
444
+ st.throws(function () {
445
+ qs.parse({}, {
446
+ decoder: 'string'
447
+ });
448
+ }, new TypeError('Decoder has to be a function.'));
449
+ st.end();
450
+ });
393
451
  });
package/test/stringify.js CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  var test = require('tape');
4
4
  var qs = require('../');
5
+ var iconv = require('iconv-lite');
5
6
 
6
7
  test('stringify()', function (t) {
7
8
  t.test('stringifies a querystring object', function (st) {
@@ -21,7 +22,7 @@ test('stringify()', function (t) {
21
22
  st.equal(qs.stringify({ a: { b: { c: { d: 'e' } } } }), 'a%5Bb%5D%5Bc%5D%5Bd%5D=e');
22
23
  st.end();
23
24
  });
24
-
25
+
25
26
  t.test('stringifies a nested object with dots notation', function (st) {
26
27
  st.equal(qs.stringify({ a: { b: 'c' } }, { allowDots: true }), 'a.b=c');
27
28
  st.equal(qs.stringify({ a: { b: { c: { d: 'e' } } } }, { allowDots: true }), 'a.b.c.d=e');
@@ -53,7 +54,7 @@ test('stringify()', function (t) {
53
54
  st.equal(qs.stringify({ a: { b: ['c', 'd'] } }), 'a%5Bb%5D%5B0%5D=c&a%5Bb%5D%5B1%5D=d');
54
55
  st.end();
55
56
  });
56
-
57
+
57
58
  t.test('stringifies a nested array value with dots notation', function (st) {
58
59
  st.equal(qs.stringify({ a: { b: ['c', 'd'] } }, { allowDots: true, encode: false }), 'a.b[0]=c&a.b[1]=d');
59
60
  st.end();
@@ -64,7 +65,12 @@ test('stringify()', function (t) {
64
65
  st.equal(qs.stringify({ a: [{ b: { c: [1] } }] }), 'a%5B0%5D%5Bb%5D%5Bc%5D%5B0%5D=1');
65
66
  st.end();
66
67
  });
67
-
68
+
69
+ t.test('stringifies an array with mixed objects and primitives', function (st) {
70
+ st.equal(qs.stringify({ a: [{ b: 1 }, 2, 3] }, { encode: false }), 'a[0][b]=1&a[1]=2&a[2]=3');
71
+ st.end();
72
+ });
73
+
68
74
  t.test('stringifies an object inside an array with dots notation', function (st) {
69
75
  st.equal(qs.stringify({ a: [{ b: 'c' }] }, { allowDots: true, encode: false }), 'a[0].b=c');
70
76
  st.equal(qs.stringify({ a: [{ b: { c: [1] } }] }, { allowDots: true, encode: false }), 'a[0].b.c[0]=1');
@@ -249,11 +255,51 @@ test('stringify()', function (t) {
249
255
  st.equal(qs.stringify({ a: 'c', z: { j: 'a', i: 'b' }, b: 'f' }, { sort: sort }), 'a=c&b=f&z%5Bi%5D=b&z%5Bj%5D=a');
250
256
  st.end();
251
257
  });
252
-
258
+
253
259
  t.test('can sort the keys at depth 3 or more too', function (st) {
254
260
  var sort = function (a, b) { return a.localeCompare(b); };
255
261
  st.equal(qs.stringify({ a: 'a', z: { zj: {zjb: 'zjb', zja: 'zja'}, zi: {zib: 'zib', zia: 'zia'} }, b: 'b' }, { sort: sort, encode: false }), 'a=a&b=b&z[zi][zia]=zia&z[zi][zib]=zib&z[zj][zja]=zja&z[zj][zjb]=zjb');
256
262
  st.equal(qs.stringify({ a: 'a', z: { zj: {zjb: 'zjb', zja: 'zja'}, zi: {zib: 'zib', zia: 'zia'} }, b: 'b' }, { sort: null, encode: false }), 'a=a&z[zj][zjb]=zjb&z[zj][zja]=zja&z[zi][zib]=zib&z[zi][zia]=zia&b=b');
257
263
  st.end();
258
264
  });
265
+
266
+ t.test('can stringify with custom encoding', function (st) {
267
+ st.equal(qs.stringify({ 県: '大阪府', '': ''}, {
268
+ encoder: function (str) {
269
+ if (str.length === 0) {
270
+ return '';
271
+ }
272
+ var buf = iconv.encode(str, 'shiftjis');
273
+ var result = [];
274
+ for (var i=0; i < buf.length; ++i) {
275
+ result.push(buf.readUInt8(i).toString(16));
276
+ }
277
+ return '%' + result.join('%');
278
+ }
279
+ }), '%8c%a7=%91%e5%8d%e3%95%7b&=');
280
+ st.end();
281
+ });
282
+
283
+ t.test('throws error with wrong encoder', function (st) {
284
+ st.throws(function () {
285
+ qs.stringify({}, {
286
+ encoder: 'string'
287
+ });
288
+ }, new TypeError('Encoder has to be a function.'));
289
+ st.end();
290
+ });
291
+
292
+ t.test('can use custom encoder for a buffer object', {
293
+ skip: typeof Buffer === 'undefined'
294
+ }, function (st) {
295
+ st.equal(qs.stringify({ a: new Buffer([1]) }, {
296
+ encoder: function (buffer) {
297
+ if (typeof buffer === 'string') {
298
+ return buffer;
299
+ }
300
+ return String.fromCharCode(buffer.readUInt8(0) + 97);
301
+ }
302
+ }), 'a=b');
303
+ st.end();
304
+ });
259
305
  });
package/.npmignore DELETED
@@ -1,18 +0,0 @@
1
- .idea
2
- *.iml
3
- npm-debug.log
4
- dump.rdb
5
- node_modules
6
- results.tap
7
- results.xml
8
- npm-shrinkwrap.json
9
- config.json
10
- .DS_Store
11
- */.DS_Store
12
- */*/.DS_Store
13
- ._*
14
- */._*
15
- */*/._*
16
- coverage.*
17
- lib-cov
18
- complexity.md
package/.travis.yml DELETED
@@ -1,69 +0,0 @@
1
- language: node_js
2
- node_js:
3
- - "5.3"
4
- - "5.2"
5
- - "5.1"
6
- - "5.0"
7
- - "4.2"
8
- - "4.1"
9
- - "4.0"
10
- - "iojs-v3.3"
11
- - "iojs-v3.2"
12
- - "iojs-v3.1"
13
- - "iojs-v3.0"
14
- - "iojs-v2.5"
15
- - "iojs-v2.4"
16
- - "iojs-v2.3"
17
- - "iojs-v2.2"
18
- - "iojs-v2.1"
19
- - "iojs-v2.0"
20
- - "iojs-v1.8"
21
- - "iojs-v1.7"
22
- - "iojs-v1.6"
23
- - "iojs-v1.5"
24
- - "iojs-v1.4"
25
- - "iojs-v1.3"
26
- - "iojs-v1.2"
27
- - "iojs-v1.1"
28
- - "iojs-v1.0"
29
- - "0.12"
30
- - "0.11"
31
- - "0.10"
32
- - "0.9"
33
- - "0.8"
34
- - "0.6"
35
- - "0.4"
36
- before_install:
37
- - 'if [ "${TRAVIS_NODE_VERSION}" != "0.9" ]; then case "$(npm --version)" in 1.*) npm install -g npm@1.4.28 ;; 2.*) npm install -g npm@2 ;; esac ; fi'
38
- - 'if [ "${TRAVIS_NODE_VERSION}" != "0.6" ] && [ "${TRAVIS_NODE_VERSION}" != "0.9" ]; then npm install -g npm; fi'
39
- script:
40
- - 'if [ "${TRAVIS_NODE_VERSION}" != "4.2" ]; then npm run tests-only ; else npm test ; fi'
41
- sudo: false
42
- matrix:
43
- fast_finish: true
44
- allow_failures:
45
- - node_js: "5.2"
46
- - node_js: "5.1"
47
- - node_js: "5.0"
48
- - node_js: "4.1"
49
- - node_js: "4.0"
50
- - node_js: "iojs-v3.2"
51
- - node_js: "iojs-v3.1"
52
- - node_js: "iojs-v3.0"
53
- - node_js: "iojs-v2.4"
54
- - node_js: "iojs-v2.3"
55
- - node_js: "iojs-v2.2"
56
- - node_js: "iojs-v2.1"
57
- - node_js: "iojs-v2.0"
58
- - node_js: "iojs-v1.7"
59
- - node_js: "iojs-v1.6"
60
- - node_js: "iojs-v1.5"
61
- - node_js: "iojs-v1.4"
62
- - node_js: "iojs-v1.3"
63
- - node_js: "iojs-v1.2"
64
- - node_js: "iojs-v1.1"
65
- - node_js: "iojs-v1.0"
66
- - node_js: "0.11"
67
- - node_js: "0.9"
68
- - node_js: "0.6"
69
- - node_js: "0.4"
package/bower.json DELETED
@@ -1,21 +0,0 @@
1
- {
2
- "name": "qs",
3
- "main": "dist/qs.js",
4
- "homepage": "https://github.com/hapijs/qs",
5
- "authors": [
6
- "Nathan LaFreniere <quitlahok@gmail.com>"
7
- ],
8
- "description": "A querystring parser that supports nesting and arrays, with a depth limit",
9
- "keywords": [
10
- "querystring",
11
- "qs"
12
- ],
13
- "license": "BSD-3-Clause",
14
- "ignore": [
15
- "**/.*",
16
- "node_modules",
17
- "bower_components",
18
- "test",
19
- "tests"
20
- ]
21
- }
package/component.json DELETED
@@ -1,15 +0,0 @@
1
- {
2
- "name": "qs",
3
- "repository": "hapijs/qs",
4
- "description": "query-string parser / stringifier with nesting support",
5
- "version": "6.1.0",
6
- "keywords": ["querystring", "query", "parser"],
7
- "main": "lib/index.js",
8
- "scripts": [
9
- "lib/index.js",
10
- "lib/parse.js",
11
- "lib/stringify.js",
12
- "lib/utils.js"
13
- ],
14
- "license": "BSD-3-Clause"
15
- }