You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

269 lines
8.0 KiB

  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) { // eslint-disable-line func-name-matching
  7. return prefix + '[]';
  8. },
  9. comma: 'comma',
  10. indices: function indices(prefix, key) { // eslint-disable-line func-name-matching
  11. return prefix + '[' + key + ']';
  12. },
  13. repeat: function repeat(prefix) { // eslint-disable-line func-name-matching
  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 defaults = {
  24. addQueryPrefix: false,
  25. allowDots: false,
  26. charset: 'utf-8',
  27. charsetSentinel: false,
  28. delimiter: '&',
  29. encode: true,
  30. encoder: utils.encode,
  31. encodeValuesOnly: false,
  32. formatter: formats.formatters[formats['default']],
  33. // deprecated
  34. indices: false,
  35. serializeDate: function serializeDate(date) { // eslint-disable-line func-name-matching
  36. return toISO.call(date);
  37. },
  38. skipNulls: false,
  39. strictNullHandling: false
  40. };
  41. var stringify = function stringify( // eslint-disable-line func-name-matching
  42. object,
  43. prefix,
  44. generateArrayPrefix,
  45. strictNullHandling,
  46. skipNulls,
  47. encoder,
  48. filter,
  49. sort,
  50. allowDots,
  51. serializeDate,
  52. formatter,
  53. encodeValuesOnly,
  54. charset
  55. ) {
  56. var obj = object;
  57. if (typeof filter === 'function') {
  58. obj = filter(prefix, obj);
  59. } else if (obj instanceof Date) {
  60. obj = serializeDate(obj);
  61. } else if (generateArrayPrefix === 'comma' && isArray(obj)) {
  62. obj = obj.join(',');
  63. }
  64. if (obj === null) {
  65. if (strictNullHandling) {
  66. return encoder && !encodeValuesOnly ? encoder(prefix, defaults.encoder, charset) : prefix;
  67. }
  68. obj = '';
  69. }
  70. if (typeof obj === 'string' || typeof obj === 'number' || typeof obj === 'boolean' || utils.isBuffer(obj)) {
  71. if (encoder) {
  72. var keyValue = encodeValuesOnly ? prefix : encoder(prefix, defaults.encoder, charset);
  73. return [formatter(keyValue) + '=' + formatter(encoder(obj, defaults.encoder, charset))];
  74. }
  75. return [formatter(prefix) + '=' + formatter(String(obj))];
  76. }
  77. var values = [];
  78. if (typeof obj === 'undefined') {
  79. return values;
  80. }
  81. var objKeys;
  82. if (isArray(filter)) {
  83. objKeys = filter;
  84. } else {
  85. var keys = Object.keys(obj);
  86. objKeys = sort ? keys.sort(sort) : keys;
  87. }
  88. for (var i = 0; i < objKeys.length; ++i) {
  89. var key = objKeys[i];
  90. if (skipNulls && obj[key] === null) {
  91. continue;
  92. }
  93. if (isArray(obj)) {
  94. pushToArray(values, stringify(
  95. obj[key],
  96. typeof generateArrayPrefix === 'function' ? generateArrayPrefix(prefix, key) : prefix,
  97. generateArrayPrefix,
  98. strictNullHandling,
  99. skipNulls,
  100. encoder,
  101. filter,
  102. sort,
  103. allowDots,
  104. serializeDate,
  105. formatter,
  106. encodeValuesOnly,
  107. charset
  108. ));
  109. } else {
  110. pushToArray(values, stringify(
  111. obj[key],
  112. prefix + (allowDots ? '.' + key : '[' + key + ']'),
  113. generateArrayPrefix,
  114. strictNullHandling,
  115. skipNulls,
  116. encoder,
  117. filter,
  118. sort,
  119. allowDots,
  120. serializeDate,
  121. formatter,
  122. encodeValuesOnly,
  123. charset
  124. ));
  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. };