@exabytellc/utils
Version:
EB react utils to make everything a little easier!
180 lines (165 loc) • 5.78 kB
JavaScript
import { handleTryCatch } from "../_helpers/handleError";
import { isArr, isEmpty, isFile, isFormData, isObj } 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, ignoreEmpty = false) {
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) {
handleTryCatch({
n: "toFormdata, _dataToFormdata",
p: { data },
e: () => {
if (ignoreEmpty && isEmpty(value)) return;
if (isArr(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 (isObj(value)) {
// Handle nested objects
for (const [key, val] of Object.entries(value)) {
_dataToFormdata(fd, val, parentKey ? `${parentKey}[${key}]` : key);
}
} else if (isFile(value)) {
// Append only valid files
if (value.size > 0 && value.name) {
fd.append(parentKey, value);
}
} else {
// Append primitive values
fd.append(parentKey, isEmpty(value) ? '' : 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);
}
}
}