stringify.js (8230B)
1 'use strict'; 2 3 var utils = require('./utils'); 4 var formats = require('./formats'); 5 var has = Object.prototype.hasOwnProperty; 6 7 var arrayPrefixGenerators = { 8 brackets: function brackets(prefix) { // eslint-disable-line func-name-matching 9 return prefix + '[]'; 10 }, 11 comma: 'comma', 12 indices: function indices(prefix, key) { // eslint-disable-line func-name-matching 13 return prefix + '[' + key + ']'; 14 }, 15 repeat: function repeat(prefix) { // eslint-disable-line func-name-matching 16 return prefix; 17 } 18 }; 19 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]); 24 }; 25 26 var toISO = Date.prototype.toISOString; 27 28 var defaults = { 29 addQueryPrefix: false, 30 allowDots: false, 31 charset: 'utf-8', 32 charsetSentinel: false, 33 delimiter: '&', 34 encode: true, 35 encoder: utils.encode, 36 encodeValuesOnly: false, 37 formatter: formats.formatters[formats['default']], 38 // deprecated 39 indices: false, 40 serializeDate: function serializeDate(date) { // eslint-disable-line func-name-matching 41 return toISO.call(date); 42 }, 43 skipNulls: false, 44 strictNullHandling: false 45 }; 46 47 var stringify = function stringify( // eslint-disable-line func-name-matching 48 object, 49 prefix, 50 generateArrayPrefix, 51 strictNullHandling, 52 skipNulls, 53 encoder, 54 filter, 55 sort, 56 allowDots, 57 serializeDate, 58 formatter, 59 encodeValuesOnly, 60 charset 61 ) { 62 var obj = object; 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)) { 68 obj = obj.join(','); 69 } 70 71 if (obj === null) { 72 if (strictNullHandling) { 73 return encoder && !encodeValuesOnly ? encoder(prefix, defaults.encoder, charset) : prefix; 74 } 75 76 obj = ''; 77 } 78 79 if (typeof obj === 'string' || typeof obj === 'number' || typeof obj === 'boolean' || utils.isBuffer(obj)) { 80 if (encoder) { 81 var keyValue = encodeValuesOnly ? prefix : encoder(prefix, defaults.encoder, charset); 82 return [formatter(keyValue) + '=' + formatter(encoder(obj, defaults.encoder, charset))]; 83 } 84 return [formatter(prefix) + '=' + formatter(String(obj))]; 85 } 86 87 var values = []; 88 89 if (typeof obj === 'undefined') { 90 return values; 91 } 92 93 var objKeys; 94 if (isArray(filter)) { 95 objKeys = filter; 96 } else { 97 var keys = Object.keys(obj); 98 objKeys = sort ? keys.sort(sort) : keys; 99 } 100 101 for (var i = 0; i < objKeys.length; ++i) { 102 var key = objKeys[i]; 103 104 if (skipNulls && obj[key] === null) { 105 continue; 106 } 107 108 if (isArray(obj)) { 109 pushToArray(values, stringify( 110 obj[key], 111 typeof generateArrayPrefix === 'function' ? generateArrayPrefix(prefix, key) : prefix, 112 generateArrayPrefix, 113 strictNullHandling, 114 skipNulls, 115 encoder, 116 filter, 117 sort, 118 allowDots, 119 serializeDate, 120 formatter, 121 encodeValuesOnly, 122 charset 123 )); 124 } else { 125 pushToArray(values, stringify( 126 obj[key], 127 prefix + (allowDots ? '.' + key : '[' + key + ']'), 128 generateArrayPrefix, 129 strictNullHandling, 130 skipNulls, 131 encoder, 132 filter, 133 sort, 134 allowDots, 135 serializeDate, 136 formatter, 137 encodeValuesOnly, 138 charset 139 )); 140 } 141 } 142 143 return values; 144 }; 145 146 var normalizeStringifyOptions = function normalizeStringifyOptions(opts) { 147 if (!opts) { 148 return defaults; 149 } 150 151 if (opts.encoder !== null && opts.encoder !== undefined && typeof opts.encoder !== 'function') { 152 throw new TypeError('Encoder has to be a function.'); 153 } 154 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'); 158 } 159 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.'); 164 } 165 format = opts.format; 166 } 167 var formatter = formats.formatters[format]; 168 169 var filter = defaults.filter; 170 if (typeof opts.filter === 'function' || isArray(opts.filter)) { 171 filter = opts.filter; 172 } 173 174 return { 175 addQueryPrefix: typeof opts.addQueryPrefix === 'boolean' ? opts.addQueryPrefix : defaults.addQueryPrefix, 176 allowDots: typeof opts.allowDots === 'undefined' ? defaults.allowDots : !!opts.allowDots, 177 charset: charset, 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, 183 filter: filter, 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 189 }; 190 }; 191 192 module.exports = function (object, opts) { 193 var obj = object; 194 var options = normalizeStringifyOptions(opts); 195 196 var objKeys; 197 var filter; 198 199 if (typeof options.filter === 'function') { 200 filter = options.filter; 201 obj = filter('', obj); 202 } else if (isArray(options.filter)) { 203 filter = options.filter; 204 objKeys = filter; 205 } 206 207 var keys = []; 208 209 if (typeof obj !== 'object' || obj === null) { 210 return ''; 211 } 212 213 var arrayFormat; 214 if (opts && opts.arrayFormat in arrayPrefixGenerators) { 215 arrayFormat = opts.arrayFormat; 216 } else if (opts && 'indices' in opts) { 217 arrayFormat = opts.indices ? 'indices' : 'repeat'; 218 } else { 219 arrayFormat = 'indices'; 220 } 221 222 var generateArrayPrefix = arrayPrefixGenerators[arrayFormat]; 223 224 if (!objKeys) { 225 objKeys = Object.keys(obj); 226 } 227 228 if (options.sort) { 229 objKeys.sort(options.sort); 230 } 231 232 for (var i = 0; i < objKeys.length; ++i) { 233 var key = objKeys[i]; 234 235 if (options.skipNulls && obj[key] === null) { 236 continue; 237 } 238 pushToArray(keys, stringify( 239 obj[key], 240 key, 241 generateArrayPrefix, 242 options.strictNullHandling, 243 options.skipNulls, 244 options.encode ? options.encoder : null, 245 options.filter, 246 options.sort, 247 options.allowDots, 248 options.serializeDate, 249 options.formatter, 250 options.encodeValuesOnly, 251 options.charset 252 )); 253 } 254 255 var joined = keys.join(options.delimiter); 256 var prefix = options.addQueryPrefix === true ? '?' : ''; 257 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&'; 262 } else { 263 // encodeURIComponent('✓') 264 prefix += 'utf8=%E2%9C%93&'; 265 } 266 } 267 268 return joined.length > 0 ? prefix + joined : ''; 269 };