UNPKG

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