source: node_modules/qs/lib/stringify.js

main
Last change on this file was d24f17c, checked in by Aleksandar Panovski <apano77@…>, 15 months ago

Initial commit

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