UNPKG

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