3 var utils = require('./utils');
4 var formats = require('./formats');
5 var has = Object.prototype.hasOwnProperty;
7 var arrayPrefixGenerators = {
8 brackets: function brackets(prefix) { // eslint-disable-line func-name-matching
12 indices: function indices(prefix, key) { // eslint-disable-line func-name-matching
13 return prefix + '[' + key + ']';
15 repeat: function repeat(prefix) { // eslint-disable-line func-name-matching
20 var isArray = Array.isArray;
21 var push = Array.prototype.push;
22 var pushToArray = function (arr, valueOrArray) {
23 push.apply(arr, isArray(valueOrArray) ? valueOrArray : [valueOrArray]);
26 var toISO = Date.prototype.toISOString;
29 addQueryPrefix: false,
32 charsetSentinel: false,
35 encoder: utils.encode,
36 encodeValuesOnly: false,
37 formatter: formats.formatters[formats['default']],
40 serializeDate: function serializeDate(date) { // eslint-disable-line func-name-matching
41 return toISO.call(date);
44 strictNullHandling: false
47 var stringify = function stringify( // eslint-disable-line func-name-matching
63 if (typeof filter === 'function') {
64 obj = filter(prefix, obj);
65 } else if (obj instanceof Date) {
66 obj = serializeDate(obj);
67 } else if (generateArrayPrefix === 'comma' && isArray(obj)) {
72 if (strictNullHandling) {
73 return encoder && !encodeValuesOnly ? encoder(prefix, defaults.encoder, charset) : prefix;
79 if (typeof obj === 'string' || typeof obj === 'number' || typeof obj === 'boolean' || utils.isBuffer(obj)) {
81 var keyValue = encodeValuesOnly ? prefix : encoder(prefix, defaults.encoder, charset);
82 return [formatter(keyValue) + '=' + formatter(encoder(obj, defaults.encoder, charset))];
84 return [formatter(prefix) + '=' + formatter(String(obj))];
89 if (typeof obj === 'undefined') {
94 if (isArray(filter)) {
97 var keys = Object.keys(obj);
98 objKeys = sort ? keys.sort(sort) : keys;
101 for (var i = 0; i < objKeys.length; ++i) {
102 var key = objKeys[i];
104 if (skipNulls && obj[key] === null) {
109 pushToArray(values, stringify(
111 typeof generateArrayPrefix === 'function' ? generateArrayPrefix(prefix, key) : prefix,
125 pushToArray(values, stringify(
127 prefix + (allowDots ? '.' + key : '[' + key + ']'),
146 var normalizeStringifyOptions = function normalizeStringifyOptions(opts) {
151 if (opts.encoder !== null && opts.encoder !== undefined && typeof opts.encoder !== 'function') {
152 throw new TypeError('Encoder has to be a function.');
155 var charset = opts.charset || defaults.charset;
156 if (typeof opts.charset !== 'undefined' && opts.charset !== 'utf-8' && opts.charset !== 'iso-8859-1') {
157 throw new TypeError('The charset option must be either utf-8, iso-8859-1, or undefined');
160 var format = formats['default'];
161 if (typeof opts.format !== 'undefined') {
162 if (!has.call(formats.formatters, opts.format)) {
163 throw new TypeError('Unknown format option provided.');
165 format = opts.format;
167 var formatter = formats.formatters[format];
169 var filter = defaults.filter;
170 if (typeof opts.filter === 'function' || isArray(opts.filter)) {
171 filter = opts.filter;
175 addQueryPrefix: typeof opts.addQueryPrefix === 'boolean' ? opts.addQueryPrefix : defaults.addQueryPrefix,
176 allowDots: typeof opts.allowDots === 'undefined' ? defaults.allowDots : !!opts.allowDots,
178 charsetSentinel: typeof opts.charsetSentinel === 'boolean' ? opts.charsetSentinel : defaults.charsetSentinel,
179 delimiter: typeof opts.delimiter === 'undefined' ? defaults.delimiter : opts.delimiter,
180 encode: typeof opts.encode === 'boolean' ? opts.encode : defaults.encode,
181 encoder: typeof opts.encoder === 'function' ? opts.encoder : defaults.encoder,
182 encodeValuesOnly: typeof opts.encodeValuesOnly === 'boolean' ? opts.encodeValuesOnly : defaults.encodeValuesOnly,
184 formatter: formatter,
185 serializeDate: typeof opts.serializeDate === 'function' ? opts.serializeDate : defaults.serializeDate,
186 skipNulls: typeof opts.skipNulls === 'boolean' ? opts.skipNulls : defaults.skipNulls,
187 sort: typeof opts.sort === 'function' ? opts.sort : null,
188 strictNullHandling: typeof opts.strictNullHandling === 'boolean' ? opts.strictNullHandling : defaults.strictNullHandling
192 module.exports = function (object, opts) {
194 var options = normalizeStringifyOptions(opts);
199 if (typeof options.filter === 'function') {
200 filter = options.filter;
201 obj = filter('', obj);
202 } else if (isArray(options.filter)) {
203 filter = options.filter;
209 if (typeof obj !== 'object' || obj === null) {
214 if (opts && opts.arrayFormat in arrayPrefixGenerators) {
215 arrayFormat = opts.arrayFormat;
216 } else if (opts && 'indices' in opts) {
217 arrayFormat = opts.indices ? 'indices' : 'repeat';
219 arrayFormat = 'indices';
222 var generateArrayPrefix = arrayPrefixGenerators[arrayFormat];
225 objKeys = Object.keys(obj);
229 objKeys.sort(options.sort);
232 for (var i = 0; i < objKeys.length; ++i) {
233 var key = objKeys[i];
235 if (options.skipNulls && obj[key] === null) {
238 pushToArray(keys, stringify(
242 options.strictNullHandling,
244 options.encode ? options.encoder : null,
248 options.serializeDate,
250 options.encodeValuesOnly,
255 var joined = keys.join(options.delimiter);
256 var prefix = options.addQueryPrefix === true ? '?' : '';
258 if (options.charsetSentinel) {
259 if (options.charset === 'iso-8859-1') {
260 // encodeURIComponent('✓'), the "numeric entity" representation of a checkmark
261 prefix += 'utf8=%26%2310003%3B&';
263 // encodeURIComponent('✓')
264 prefix += 'utf8=%E2%9C%93&';
268 return joined.length > 0 ? prefix + joined : '';