1'use strict'; 2 3var utils = require('./utils'); 4var formats = require('./formats'); 5 6var arrayPrefixGenerators = { 7 brackets: function brackets(prefix) { // eslint-disable-line func-name-matching 8 return prefix + '[]'; 9 }, 10 indices: function indices(prefix, key) { // eslint-disable-line func-name-matching 11 return prefix + '[' + key + ']'; 12 }, 13 repeat: function repeat(prefix) { // eslint-disable-line func-name-matching 14 return prefix; 15 } 16}; 17 18var toISO = Date.prototype.toISOString; 19 20var defaults = { 21 delimiter: '&', 22 encode: true, 23 encoder: utils.encode, 24 encodeValuesOnly: false, 25 serializeDate: function serializeDate(date) { // eslint-disable-line func-name-matching 26 return toISO.call(date); 27 }, 28 skipNulls: false, 29 strictNullHandling: false 30}; 31 32var stringify = function stringify( // eslint-disable-line func-name-matching 33 object, 34 prefix, 35 generateArrayPrefix, 36 strictNullHandling, 37 skipNulls, 38 encoder, 39 filter, 40 sort, 41 allowDots, 42 serializeDate, 43 formatter, 44 encodeValuesOnly 45) { 46 var obj = object; 47 if (typeof filter === 'function') { 48 obj = filter(prefix, obj); 49 } else if (obj instanceof Date) { 50 obj = serializeDate(obj); 51 } else if (obj === null) { 52 if (strictNullHandling) { 53 return encoder && !encodeValuesOnly ? encoder(prefix, defaults.encoder) : prefix; 54 } 55 56 obj = ''; 57 } 58 59 if (typeof obj === 'string' || typeof obj === 'number' || typeof obj === 'boolean' || utils.isBuffer(obj)) { 60 if (encoder) { 61 var keyValue = encodeValuesOnly ? prefix : encoder(prefix, defaults.encoder); 62 return [formatter(keyValue) + '=' + formatter(encoder(obj, defaults.encoder))]; 63 } 64 return [formatter(prefix) + '=' + formatter(String(obj))]; 65 } 66 67 var values = []; 68 69 if (typeof obj === 'undefined') { 70 return values; 71 } 72 73 var objKeys; 74 if (Array.isArray(filter)) { 75 objKeys = filter; 76 } else { 77 var keys = Object.keys(obj); 78 objKeys = sort ? keys.sort(sort) : keys; 79 } 80 81 for (var i = 0; i < objKeys.length; ++i) { 82 var key = objKeys[i]; 83 84 if (skipNulls && obj[key] === null) { 85 continue; 86 } 87 88 if (Array.isArray(obj)) { 89 values = values.concat(stringify( 90 obj[key], 91 generateArrayPrefix(prefix, key), 92 generateArrayPrefix, 93 strictNullHandling, 94 skipNulls, 95 encoder, 96 filter, 97 sort, 98 allowDots, 99 serializeDate, 100 formatter, 101 encodeValuesOnly 102 )); 103 } else { 104 values = values.concat(stringify( 105 obj[key], 106 prefix + (allowDots ? '.' + key : '[' + key + ']'), 107 generateArrayPrefix, 108 strictNullHandling, 109 skipNulls, 110 encoder, 111 filter, 112 sort, 113 allowDots, 114 serializeDate, 115 formatter, 116 encodeValuesOnly 117 )); 118 } 119 } 120 121 return values; 122}; 123 124module.exports = function (object, opts) { 125 var obj = object; 126 var options = opts ? utils.assign({}, opts) : {}; 127 128 if (options.encoder !== null && options.encoder !== undefined && typeof options.encoder !== 'function') { 129 throw new TypeError('Encoder has to be a function.'); 130 } 131 132 var delimiter = typeof options.delimiter === 'undefined' ? defaults.delimiter : options.delimiter; 133 var strictNullHandling = typeof options.strictNullHandling === 'boolean' ? options.strictNullHandling : defaults.strictNullHandling; 134 var skipNulls = typeof options.skipNulls === 'boolean' ? options.skipNulls : defaults.skipNulls; 135 var encode = typeof options.encode === 'boolean' ? options.encode : defaults.encode; 136 var encoder = typeof options.encoder === 'function' ? options.encoder : defaults.encoder; 137 var sort = typeof options.sort === 'function' ? options.sort : null; 138 var allowDots = typeof options.allowDots === 'undefined' ? false : options.allowDots; 139 var serializeDate = typeof options.serializeDate === 'function' ? options.serializeDate : defaults.serializeDate; 140 var encodeValuesOnly = typeof options.encodeValuesOnly === 'boolean' ? options.encodeValuesOnly : defaults.encodeValuesOnly; 141 if (typeof options.format === 'undefined') { 142 options.format = formats['default']; 143 } else if (!Object.prototype.hasOwnProperty.call(formats.formatters, options.format)) { 144 throw new TypeError('Unknown format option provided.'); 145 } 146 var formatter = formats.formatters[options.format]; 147 var objKeys; 148 var filter; 149 150 if (typeof options.filter === 'function') { 151 filter = options.filter; 152 obj = filter('', obj); 153 } else if (Array.isArray(options.filter)) { 154 filter = options.filter; 155 objKeys = filter; 156 } 157 158 var keys = []; 159 160 if (typeof obj !== 'object' || obj === null) { 161 return ''; 162 } 163 164 var arrayFormat; 165 if (options.arrayFormat in arrayPrefixGenerators) { 166 arrayFormat = options.arrayFormat; 167 } else if ('indices' in options) { 168 arrayFormat = options.indices ? 'indices' : 'repeat'; 169 } else { 170 arrayFormat = 'indices'; 171 } 172 173 var generateArrayPrefix = arrayPrefixGenerators[arrayFormat]; 174 175 if (!objKeys) { 176 objKeys = Object.keys(obj); 177 } 178 179 if (sort) { 180 objKeys.sort(sort); 181 } 182 183 for (var i = 0; i < objKeys.length; ++i) { 184 var key = objKeys[i]; 185 186 if (skipNulls && obj[key] === null) { 187 continue; 188 } 189 190 keys = keys.concat(stringify( 191 obj[key], 192 key, 193 generateArrayPrefix, 194 strictNullHandling, 195 skipNulls, 196 encode ? encoder : null, 197 filter, 198 sort, 199 allowDots, 200 serializeDate, 201 formatter, 202 encodeValuesOnly 203 )); 204 } 205 206 var joined = keys.join(delimiter); 207 var prefix = options.addQueryPrefix === true ? '?' : ''; 208 209 return joined.length > 0 ? prefix + joined : ''; 210}; 211