stringify.js 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  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. formatter,
  62. encodeValuesOnly,
  63. charset
  64. ) {
  65. var obj = object;
  66. if (typeof filter === 'function') {
  67. obj = filter(prefix, obj);
  68. } else if (obj instanceof Date) {
  69. obj = serializeDate(obj);
  70. } else if (generateArrayPrefix === 'comma' && isArray(obj)) {
  71. obj = utils.maybeMap(obj, function (value) {
  72. if (value instanceof Date) {
  73. return serializeDate(value);
  74. }
  75. return value;
  76. }).join(',');
  77. }
  78. if (obj === null) {
  79. if (strictNullHandling) {
  80. return encoder && !encodeValuesOnly ? encoder(prefix, defaults.encoder, charset, 'key') : prefix;
  81. }
  82. obj = '';
  83. }
  84. if (isNonNullishPrimitive(obj) || utils.isBuffer(obj)) {
  85. if (encoder) {
  86. var keyValue = encodeValuesOnly ? prefix : encoder(prefix, defaults.encoder, charset, 'key');
  87. return [formatter(keyValue) + '=' + formatter(encoder(obj, defaults.encoder, charset, 'value'))];
  88. }
  89. return [formatter(prefix) + '=' + formatter(String(obj))];
  90. }
  91. var values = [];
  92. if (typeof obj === 'undefined') {
  93. return values;
  94. }
  95. var objKeys;
  96. if (isArray(filter)) {
  97. objKeys = filter;
  98. } else {
  99. var keys = Object.keys(obj);
  100. objKeys = sort ? keys.sort(sort) : keys;
  101. }
  102. for (var i = 0; i < objKeys.length; ++i) {
  103. var key = objKeys[i];
  104. var value = obj[key];
  105. if (skipNulls && value === null) {
  106. continue;
  107. }
  108. var keyPrefix = isArray(obj)
  109. ? typeof generateArrayPrefix === 'function' ? generateArrayPrefix(prefix, key) : prefix
  110. : prefix + (allowDots ? '.' + key : '[' + key + ']');
  111. pushToArray(values, stringify(
  112. value,
  113. keyPrefix,
  114. generateArrayPrefix,
  115. strictNullHandling,
  116. skipNulls,
  117. encoder,
  118. filter,
  119. sort,
  120. allowDots,
  121. serializeDate,
  122. formatter,
  123. encodeValuesOnly,
  124. charset
  125. ));
  126. }
  127. return values;
  128. };
  129. var normalizeStringifyOptions = function normalizeStringifyOptions(opts) {
  130. if (!opts) {
  131. return defaults;
  132. }
  133. if (opts.encoder !== null && opts.encoder !== undefined && typeof opts.encoder !== 'function') {
  134. throw new TypeError('Encoder has to be a function.');
  135. }
  136. var charset = opts.charset || defaults.charset;
  137. if (typeof opts.charset !== 'undefined' && opts.charset !== 'utf-8' && opts.charset !== 'iso-8859-1') {
  138. throw new TypeError('The charset option must be either utf-8, iso-8859-1, or undefined');
  139. }
  140. var format = formats['default'];
  141. if (typeof opts.format !== 'undefined') {
  142. if (!has.call(formats.formatters, opts.format)) {
  143. throw new TypeError('Unknown format option provided.');
  144. }
  145. format = opts.format;
  146. }
  147. var formatter = formats.formatters[format];
  148. var filter = defaults.filter;
  149. if (typeof opts.filter === 'function' || isArray(opts.filter)) {
  150. filter = opts.filter;
  151. }
  152. return {
  153. addQueryPrefix: typeof opts.addQueryPrefix === 'boolean' ? opts.addQueryPrefix : defaults.addQueryPrefix,
  154. allowDots: typeof opts.allowDots === 'undefined' ? defaults.allowDots : !!opts.allowDots,
  155. charset: charset,
  156. charsetSentinel: typeof opts.charsetSentinel === 'boolean' ? opts.charsetSentinel : defaults.charsetSentinel,
  157. delimiter: typeof opts.delimiter === 'undefined' ? defaults.delimiter : opts.delimiter,
  158. encode: typeof opts.encode === 'boolean' ? opts.encode : defaults.encode,
  159. encoder: typeof opts.encoder === 'function' ? opts.encoder : defaults.encoder,
  160. encodeValuesOnly: typeof opts.encodeValuesOnly === 'boolean' ? opts.encodeValuesOnly : defaults.encodeValuesOnly,
  161. filter: filter,
  162. formatter: formatter,
  163. serializeDate: typeof opts.serializeDate === 'function' ? opts.serializeDate : defaults.serializeDate,
  164. skipNulls: typeof opts.skipNulls === 'boolean' ? opts.skipNulls : defaults.skipNulls,
  165. sort: typeof opts.sort === 'function' ? opts.sort : null,
  166. strictNullHandling: typeof opts.strictNullHandling === 'boolean' ? opts.strictNullHandling : defaults.strictNullHandling
  167. };
  168. };
  169. module.exports = function (object, opts) {
  170. var obj = object;
  171. var options = normalizeStringifyOptions(opts);
  172. var objKeys;
  173. var filter;
  174. if (typeof options.filter === 'function') {
  175. filter = options.filter;
  176. obj = filter('', obj);
  177. } else if (isArray(options.filter)) {
  178. filter = options.filter;
  179. objKeys = filter;
  180. }
  181. var keys = [];
  182. if (typeof obj !== 'object' || obj === null) {
  183. return '';
  184. }
  185. var arrayFormat;
  186. if (opts && opts.arrayFormat in arrayPrefixGenerators) {
  187. arrayFormat = opts.arrayFormat;
  188. } else if (opts && 'indices' in opts) {
  189. arrayFormat = opts.indices ? 'indices' : 'repeat';
  190. } else {
  191. arrayFormat = 'indices';
  192. }
  193. var generateArrayPrefix = arrayPrefixGenerators[arrayFormat];
  194. if (!objKeys) {
  195. objKeys = Object.keys(obj);
  196. }
  197. if (options.sort) {
  198. objKeys.sort(options.sort);
  199. }
  200. for (var i = 0; i < objKeys.length; ++i) {
  201. var key = objKeys[i];
  202. if (options.skipNulls && obj[key] === null) {
  203. continue;
  204. }
  205. pushToArray(keys, stringify(
  206. obj[key],
  207. key,
  208. generateArrayPrefix,
  209. options.strictNullHandling,
  210. options.skipNulls,
  211. options.encode ? options.encoder : null,
  212. options.filter,
  213. options.sort,
  214. options.allowDots,
  215. options.serializeDate,
  216. options.formatter,
  217. options.encodeValuesOnly,
  218. options.charset
  219. ));
  220. }
  221. var joined = keys.join(options.delimiter);
  222. var prefix = options.addQueryPrefix === true ? '?' : '';
  223. if (options.charsetSentinel) {
  224. if (options.charset === 'iso-8859-1') {
  225. // encodeURIComponent('&#10003;'), the "numeric entity" representation of a checkmark
  226. prefix += 'utf8=%26%2310003%3B&';
  227. } else {
  228. // encodeURIComponent('✓')
  229. prefix += 'utf8=%E2%9C%93&';
  230. }
  231. }
  232. return joined.length > 0 ? prefix + joined : '';
  233. };