UNPKG

xuxi

Version:

Dynamically utility for combining different types of values ​​into a single value.

145 lines (144 loc) 5.86 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.object = void 0; exports.isPlainObject = isPlainObject; exports.clean = clean; /** * Checks if a given value is a plain object (i.e., not an array or null). * @param value - The value to check. * @returns True if the value is a plain object, otherwise false. */ function isPlainObject(value) { return value !== null && typeof value === 'object' && !Array.isArray(value); } /** * Merges multiple objects deeply, handling arrays and functions gracefully. * @template T - The base object type. * @param obj - One or more objects to merge. * @returns The deeply merged object. */ function baseObject(...obj) { const seen = new WeakMap(); // Use WeakMap to store processed objects function merge(acc, input) { if (!input) return acc; if (isPlainObject(input)) { if (seen.has(input)) return seen.get(input); // If there is one, use previous result. const newAcc = { ...acc }; // Copy acc so as not to change direct references seen.set(input, newAcc); // Mark objects as processed acc = newAcc; // Use copied version } if (Array.isArray(input)) return { ...acc, ...baseObject(...input) }; if (typeof input === 'function') { const result = input(acc); return isPlainObject(result) ? merge(acc, result) : { ...acc, ...baseObject(result) }; } if (isPlainObject(input)) { Reflect.ownKeys(input).forEach(key => { const value = input[key]; if (isPlainObject(value) && isPlainObject(acc[key])) { acc[key] = merge(acc[key], value); } else { acc[key] = value; } }); return acc; } return acc; } return obj.reduce((acc, input) => merge(acc, input), {}); } /** * Merges multiple objects deeply, handling arrays and functions gracefully **without overwriting**. * @template T - The base object type. * @param obj - One or more objects to merge. * @returns The deeply merged object **without overwriting** the value at the first key, only change the value if it does not exist. */ function preserveRoot(...obj) { const seen = new WeakMap(); function merge(acc, input) { if (!input) return acc; if (isPlainObject(input)) { if (seen.has(input)) return seen.get(input); const newAcc = { ...acc }; seen.set(input, newAcc); acc = newAcc; } if (Array.isArray(input)) return { ...acc, ...preserveRoot(...input) }; if (typeof input === 'function') { const result = input(acc); return isPlainObject(result) ? merge(acc, result) : { ...acc, ...preserveRoot(result) }; } if (isPlainObject(input)) { Reflect.ownKeys(input).forEach(key => { const value = input[key]; if (acc[key] === undefined) { acc[key] = value; // Only change the value if it does not exist } else if (isPlainObject(value) && isPlainObject(acc[key])) { acc[key] = merge(acc[key], value); } }); return acc; } return acc; } return obj.reduce((acc, input) => merge(acc, input), {}); } /** * Recursively removes falsy values from an object, except those specified in `exclude`. * @template T - The object type. * @param obj - The object to clean. * @param exclude - An array of values to be preserved even if they are falsy (default: `[]`). * @param seen - To detect cyclic references (default: `new WeakSet<object>()`). * @returns A new object without the falsy values. * @example * @see {@link https://ilkhoeri.github.io/xuxi/clean Docs} */ function clean(obj, exclude = [], seen = new WeakSet()) { const excludeSet = new Set(exclude); if (seen.has(obj)) return obj; // Avoid infinite loops seen.add(obj); // Mark object as visited return Reflect.ownKeys(obj).reduce((acc, key) => { const value = obj[key]; if (isPlainObject(value)) { const cleanedObject = clean(value, exclude, seen); // Clean objects recursively // Ensure the object is not empty before inserting if (Object.keys(cleanedObject).length > 0 || typeof key === 'symbol') acc[key] = cleanedObject; } else if (Array.isArray(value)) { // Clear every element in the array, remove empty objects const cleanedArray = value .map(item => (isPlainObject(item) ? clean(item, exclude, seen) : item)) .filter(item => (item && !(isPlainObject(item) && Object.keys(item).length === 0)) || excludeSet.has(item)); if (cleanedArray.length > 0) acc[key] = cleanedArray; } else if (value || excludeSet.has(value) || typeof key === 'symbol') { // Save the value if it is not falsy or belongs to `excludeSet` acc[key] = value; } return acc; }, {}); } /** * Recursively merge objects with support for arrays, dynamic functions, and non falsy properties into a single object. * * Provides a chaining: * - {@link baseObject raw} method to **get falsy values** from the result. * - {@link preserveRoot preserve} method to join **without overwriting** first value. * @example * @see {@link https://ilkhoeri.github.io/xuxi/?id=object Docs} */ const object = (...obj) => clean(baseObject(...obj), [0]); exports.object = object; exports.object.raw = baseObject; exports.object.preserve = preserveRoot;