UNPKG

dbl-utils

Version:

Utilities for dbl, adba and others projects

221 lines (202 loc) 7.39 kB
type MutationFunction = (key: string, value: any, data: any) => any; interface MergeOptions { mutation: MutationFunction; ommit?: string[]; data?: any; } interface ConfigOptions { fix?: (target: any, source: any) => any; } interface TransformOptions { beforeFunc?: (context: Context) => any; duringFunc?: (context: Context) => any; afterFunc?: (context: Context) => any; nonObjectFunc?: (context: Context) => any; filter?: string | string[] | ((context: Context) => boolean); key?: string; root?: any; } interface Context { input: any; key: string | null; parentObj: any; parentKey: string | null; path: string; } let useConfig: ConfigOptions = {}; /** * Check if an item is a simple object. * @param item - The item to check. * @returns True if the item is an object, false otherwise. */ function isObject(item: any): boolean { return item && typeof item === 'object' && !Array.isArray(item); } /** * Deeply merge objects into the target object. * @param target - The target object where to merge. * @param sources - The sources to be merged into the target. * @returns The merged target object. */ function effectiveDeepMerge(target: any, ...sources: any[]): any { if (!sources.length) return target; const source = sources.shift(); let fixValue; if (typeof useConfig.fix === 'function') { fixValue = useConfig.fix(target, source); } if (fixValue !== undefined) target = fixValue; else if (isObject(target) && isObject(source)) { for (const key in source) { if (isObject(source[key])) { if (!target[key]) Object.assign(target, { [key]: {} }); effectiveDeepMerge(target[key], source[key]); } else if (typeof source[key] !== 'undefined') { Object.assign(target, { [key]: source[key] }); } } } return effectiveDeepMerge(target, ...sources); } /** * Execute a mutation function on the object and merge results. * @param target - The target object to be merged with mutation. * @param options - Options for mutation, ommit keys and additional data. * @param parentKey - Internal tracking key for recursion. * @returns The mutated and merged target object. * * @example * ```ts * await mergeWithMutation({ a: { b: 1 } }, { mutation: () => ({ c: 2 }) }); * // => { a: { b: 1, c: 2 } } * ``` */ export async function mergeWithMutation( target: any, { mutation, ommit = [], data }: MergeOptions, parentKey = '' ): Promise<any> { for (const key in target) { if (!ommit.includes(key) && typeof target[key] === 'object') { const keyFixed = Array.isArray(target) ? parentKey + '.' + key : key; const mergeResult = await mutation(keyFixed, target[key], data); if (mergeResult) effectiveDeepMerge(target[key], mergeResult); await mergeWithMutation(target[key], { mutation, ommit, data }, key); } } return target; } /** * Deep merge multiple objects. * @param target - The target object where other objects will be merged. * @param sources - Other objects to be merged into the target. * @returns The merged object. * * @example * ```ts * deepMerge({}, { a: 1 }, { b: 2 }); // { a: 1, b: 2 } * ``` */ export function deepMerge(target: any, ...sources: any[]): any { const mergedObject = effectiveDeepMerge(target, ...sources); useConfig = {}; return mergedObject; } /** * Configure deep merge behavior. Useful for setting a custom * fixer function before calling {@link deepMerge}. * * @param config - Configuration options. * * @example * ```ts * deepMerge.setConfig({ fix: () => ({ fixed: true }) }); * deepMerge({}, { a: 1 }); // { fixed: true } * ``` */ deepMerge.setConfig = (config: ConfigOptions) => { useConfig = config; }; /** * Transforms and flattens a JSON object. * * @param json - The JSON object to be transformed. * @param options - Options for the mutation functions. * @returns An array with the modified object and the flattened object. * * @example * ```ts * transformJson({ a: { b: 1 } }, { filter: 'a' }); * // => [{ a: { b: 1 } }, { a: { b: 1 } }] * ``` */ export function transformJson(json: any, options: TransformOptions = {}): [any, any] { const objCopy = JSON.parse(JSON.stringify(json)); const flattenObj: Record<string, any> = {}; const recursive = ( obj: any, key: string | null = null, parentObj: any = null, parentKey: string | null = null, path: string = '' ) => { if (typeof options.beforeFunc === 'function') { const mutation = options.beforeFunc({ input: obj, key, parentObj, parentKey, path }); if (mutation && typeof mutation === 'object') { if (mutation.replace) parentObj[key as string] = mutation.replace; else if (mutation.merge) deepMerge(obj, mutation.merge); else if (mutation.delete) delete parentObj[key as string]; } } for (const innerKey in obj) { const value = obj[innerKey]; const newPath = path ? `${path}.${innerKey}` : innerKey; if (typeof options.filter === 'string' && innerKey === options.filter) { flattenObj[newPath] = value; continue; } else if (Array.isArray(options.filter) && options.filter.includes(innerKey)) { flattenObj[newPath] = value; continue; } else if ( typeof options.filter === 'function' && !options.filter({ input: value, key: innerKey, parentObj: obj, parentKey: key, path: newPath }) ) { flattenObj[newPath] = value; continue; } if (typeof value === 'object' && value !== null) { if (typeof options.duringFunc === 'function') { const mutation = options.duringFunc({ input: value, key: innerKey, parentObj: obj, parentKey: key, path: newPath }); if (mutation && typeof mutation === 'object') { if (mutation.replace) obj[innerKey] = mutation.replace; else if (mutation.merge) obj[innerKey] = deepMerge(value, mutation.merge); else if (mutation.delete) { delete obj[innerKey]; continue; } } } recursive(value, innerKey, obj, key, newPath); } else { if (options.nonObjectFunc) { const mutation = options.nonObjectFunc({ input: value, key: innerKey, parentObj: obj, parentKey: key, path: newPath }); if (mutation && typeof mutation === 'object') { if (mutation.replace) obj[innerKey] = mutation.replace; else if (mutation.merge) obj[innerKey] += mutation.merge; else if (mutation.delete) { delete obj[innerKey]; continue; } } } flattenObj[newPath] = value; } } if (typeof options.afterFunc === 'function') { const mutation = options.afterFunc({ input: obj, key, parentObj, parentKey, path }); if (mutation && typeof mutation === 'object') { if (mutation.replace) parentObj[key as string] = mutation.replace; else if (mutation.merge) deepMerge(obj, mutation.merge); else if (mutation.delete) delete parentObj[key as string]; } } }; const rootKey = options.key || '_root'; const rootObj: any = options.root || {}; recursive(objCopy, rootKey, rootObj); return [objCopy, rootObj[rootKey]]; }