UNPKG

@exabytellc/utils

Version:

EB react utils to make everything a little easier!

176 lines (161 loc) 5.62 kB
import { handleTryCatch } from "../_helpers/handleError"; import { isFormData } from "../types"; /** * Converts a JavaScript object or array into a FormData instance. * * @param {Object|Array} data - The data to convert to FormData. * @returns {FormData} - The resulting FormData instance. */ export function toFormdata(data) { return handleTryCatch({ n: "toFormdata", p: { data }, e: () => { const fd = new FormData(); _dataToFormdata(fd, data, ""); // No root key return fd; } }); /** * Recursively converts data into FormData format. * * @param {FormData} fd - The FormData instance being populated. * @param {*} value - The value to process. * @param {string} parentKey - The key path in the FormData structure. */ function _dataToFormdata(fd, value, parentKey) { if (value === null || value === undefined) { return; // Ignore null/undefined values } if (Array.isArray(value)) { // If the root is an array, avoid wrapping it inside a key value.forEach((val, i) => { _dataToFormdata(fd, val, `${parentKey ? parentKey + "[" + i + "]" : i}`); }); } else if (typeof value === "object" && !(value instanceof File)) { // Handle nested objects for (const [key, val] of Object.entries(value)) { _dataToFormdata(fd, val, parentKey ? `${parentKey}[${key}]` : key); } } else if (value instanceof File) { // Append only valid files if (value.size > 0 && value.name) { fd.append(parentKey, value); } } else { // Append primitive values fd.append(parentKey, value); } } } /** * Converts a FormData instance back into a JavaScript object or array. * * @param {FormData} formData - The FormData instance to convert. * @returns {Object|Array|null} - The resulting object or array. */ export function fromFormdata(formData) { return handleTryCatch({ n: "fromFormdata", c: !isFormData(formData, true), p: { formData }, e: () => { if (!formData) return null; const object = {}; for (const [key, value] of formData.entries()) { parseKey(object, key, value); } // If the object is purely an array (e.g., `{ "0": "A", "1": "B" }`), convert it to an actual array if (isPureArray(object)) { return Object.values(object); } return object; } }); /** * Parses a FormData key into a nested object structure. * * @param {Object} obj - The object being populated. * @param {string} key - The FormData key. * @param {*} value - The value to assign. */ function parseKey(obj, key, value) { const parts = key.replace(/\]/g, "").split("["); let current = obj; parts.forEach((part, index) => { if (index === parts.length - 1) { // Final key, assign value if (current[part] !== undefined) { current[part] = [].concat(current[part], value); } else { current[part] = value; } } else { // Create nested object/array if needed if (!current[part]) { current[part] = isNaN(parts[index + 1]) ? {} : []; } current = current[part]; } }); } /** * Checks if an object is purely an array (i.e., its keys are sequential numbers). * * @param {Object} obj - The object to check. * @returns {boolean} - True if the object is purely an array, false otherwise. */ function isPureArray(obj) { const keys = Object.keys(obj); return keys.every((key, i) => String(i) === key); // Ensures keys are sequential numbers } } /** * Merges multiple objects, arrays, or FormData instances into a single FormData object. * * @param {...(Object|Array|FormData)} sources - The sources to merge. Can be objects, arrays, or FormData. * @returns {FormData} - A new FormData instance containing all merged data. */ export function mergeFormData(...sources) { return handleTryCatch({ n: "mergeFormData", c: !sources?.length, p: { sources }, e: () => { const formData = new FormData(); for (const data of sources) { appendData(formData, data); } return formData; } }); /** * Recursively appends data to a FormData instance. * Handles objects, arrays, FormData, and primitive values correctly. * * @param {FormData} fd - The FormData object to append data to. * @param {*} data - The data to append (object, array, FormData, string, number, file, etc.). * @param {string} [parentKey=""] - The parent key for nested structures. */ function appendData(fd, data, parentKey = "") { if (data == null) return; // Ignore null/undefined values if (data instanceof FormData) { // Handle merging FormData, ensuring keys don't conflict for (const [key, value] of data.entries()) { fd.append(parentKey ? `${parentKey}[${key}]` : key, value); } } else if (typeof data === "object" && !(data instanceof File) && !(data instanceof Blob)) { // Handle objects & arrays for (const key in data) { if (Object.prototype.hasOwnProperty.call(data, key)) { const value = data[key]; const newKey = parentKey ? `${parentKey}[${key}]` : key; appendData(fd, value, newKey); } } } else { // Append primitive values or files fd.append(parentKey, data); } } }