stringify.js 8.2 KB

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