'use strict'; import utils from '../utils.js'; import AxiosError from '../core/AxiosError.js'; // temporary hotfix to avoid circular references until AxiosURLSearchParams is refactored import PlatformFormData from '../platform/node/classes/FormData.js'; /** * Determines if the given thing is a array or js object. * * @param {string} thing - The object or array to be visited. * * @returns {boolean} */ function isVisitable(thing) { return utils.isPlainObject(thing) || utils.isArray(thing); } /** * It removes the brackets from the end of a string * * @param {string} key - The key of the parameter. * * @returns {string} the key without the brackets. */ function removeBrackets(key) { return utils.endsWith(key, '[]') ? key.slice(0, -2) : key; } /** * It takes a path, a key, and a boolean, and returns a string * * @param {string} path - The path to the current key. * @param {string} key - The key of the current object being iterated over. * @param {string} dots - If true, the key will be rendered with dots instead of brackets. * * @returns {string} The path to the current key. */ function renderKey(path, key, dots) { if (!path) return key; return path.concat(key).map(function each(token, i) { // eslint-disable-next-line no-param-reassign token = removeBrackets(token); return !dots && i ? '[' + token + ']' : token; }).join(dots ? '.' : ''); } /** * If the array is an array and none of its elements are visitable, then it's a flat array. * * @param {Array} arr - The array to check * * @returns {boolean} */ function isFlatArray(arr) { return utils.isArray(arr) && !arr.some(isVisitable); } const predicates = utils.toFlatObject(utils, {}, null, function filter(prop) { return /^is[A-Z]/.test(prop); }); /** * Convert a data object to FormData * * @param {Object} obj * @param {?Object} [formData] * @param {?Object} [options] * @param {Function} [options.visitor] * @param {Boolean} [options.metaTokens = true] * @param {Boolean} [options.dots = false] * @param {?Boolean} [options.indexes = false] * * @returns {Object} **/ /** * It converts an object into a FormData object * * @param {Object} obj - The object to convert to form data. * @param {string} formData - The FormData object to append to. * @param {Object} options * * @returns */ function toFormData(obj, formData, options) { if (!utils.isObject(obj)) { throw new TypeError('target must be an object'); } // eslint-disable-next-line no-param-reassign formData = formData || new (PlatformFormData || FormData)(); // eslint-disable-next-line no-param-reassign options = utils.toFlatObject(options, { metaTokens: true, dots: false, indexes: false }, false, function defined(option, source) { // eslint-disable-next-line no-eq-null,eqeqeq return !utils.isUndefined(source[option]); }); const metaTokens = options.metaTokens; // eslint-disable-next-line no-use-before-define const visitor = options.visitor || defaultVisitor; const dots = options.dots; const indexes = options.indexes; const _Blob = options.Blob || typeof Blob !== 'undefined' && Blob; const useBlob = _Blob && utils.isSpecCompliantForm(formData); if (!utils.isFunction(visitor)) { throw new TypeError('visitor must be a function'); } function convertValue(value) { if (value === null) return ''; if (utils.isDate(value)) { return value.toISOString(); } if (!useBlob && utils.isBlob(value)) { throw new AxiosError('Blob is not supported. Use a Buffer instead.'); } if (utils.isArrayBuffer(value) || utils.isTypedArray(value)) { return useBlob && typeof Blob === 'function' ? new Blob([value]) : Buffer.from(value); } return value; } /** * Default visitor. * * @param {*} value * @param {String|Number} key * @param {Array} path * @this {FormData} * * @returns {boolean} return true to visit the each prop of the value recursively */ function defaultVisitor(value, key, path) { let arr = value; if (value && !path && typeof value === 'object') { if (utils.endsWith(key, '{}')) { // eslint-disable-next-line no-param-reassign key = metaTokens ? key : key.slice(0, -2); // eslint-disable-next-line no-param-reassign value = JSON.stringify(value); } else if ( (utils.isArray(value) && isFlatArray(value)) || ((utils.isFileList(value) || utils.endsWith(key, '[]')) && (arr = utils.toArray(value)) )) { // eslint-disable-next-line no-param-reassign key = removeBrackets(key); arr.forEach(function each(el, index) { !(utils.isUndefined(el) || el === null) && formData.append( // eslint-disable-next-line no-nested-ternary indexes === true ? renderKey([key], index, dots) : (indexes === null ? key : key + '[]'), convertValue(el) ); }); return false; } } if (isVisitable(value)) { return true; } formData.append(renderKey(path, key, dots), convertValue(value)); return false; } const stack = []; const exposedHelpers = Object.assign(predicates, { defaultVisitor, convertValue, isVisitable }); function build(value, path) { if (utils.isUndefined(value)) return; if (stack.indexOf(value) !== -1) { throw Error('Circular reference detected in ' + path.join('.')); } stack.push(value); utils.forEach(value, function each(el, key) { const result = !(utils.isUndefined(el) || el === null) && visitor.call( formData, el, utils.isString(key) ? key.trim() : key, path, exposedHelpers ); if (result === true) { build(el, path ? path.concat(key) : [key]); } }); stack.pop(); } if (!utils.isObject(obj)) { throw new TypeError('data must be an object'); } build(obj); return formData; } export default toFormData;