UNPKG

merge-deep-ts

Version:

Deep fast merge JavaScript objects with circular references handling and TypeScript support

142 lines (141 loc) 5.17 kB
import { ERROR_NOT_ARRAY, getCounter, getType, isMergeable, isNullable, } from "./utils.js"; import { MergeableType } from "./types.js"; /** * Merge objects with circular references * @param result - result object * @param args - array of objects to merge * @param cache - cached results of merging. Key - sorted string with ids of objects (e.g. "1:3:4:9"), value - result of merging * @param objects - map of traversed objects to their ids. Key - object, value - id */ function mergeCircular(result, args, cache, objects) { var _a; if (!Array.isArray(args)) { throw new Error(ERROR_NOT_ARRAY); } if (args.length === 0) { result.value = null; return; } // filter out null and undefined const nonNullableArgs = args.filter(obj => !isNullable(obj)); if (nonNullableArgs.length === 0) { result.value = args[args.length - 1]; return; } // store mergeable arguments in the `objects` map for (const obj of nonNullableArgs) { if (!objects.has(obj) && isMergeable(obj)) { objects.set(obj, getCounter()); } } // if there is only one or zero arguments, return it if (nonNullableArgs.length <= 1) { result.value = nonNullableArgs[0]; return; } const lastArg = nonNullableArgs[nonNullableArgs.length - 1]; // if last argument isn't mergeable, return it const type = getType(lastArg); if (type === MergeableType.NonMergeable || type === MergeableType.Nullable) { result.value = lastArg; return; } // collect all mergeable arguments with the same type from the end let count = 1; for (let i = nonNullableArgs.length - 2; i >= 0; i--) { if (type !== getType(nonNullableArgs[i])) { break; } count++; } const mergeableArgs = nonNullableArgs.slice(-count); // check if we already have the result in cache const ids = mergeableArgs .map(obj => objects.get(obj)) .sort() .join(":"); const cached = (_a = cache.get(ids)) === null || _a === void 0 ? void 0 : _a.value; if (cached) { result.value = cached; return; } else { // store result in cache cache.set(ids, result); } // merge all mergeable arguments switch (type) { case MergeableType.Object: { // find all keys const keys = mergeableArgs.reduce((acc, obj) => { for (const key of [...Object.getOwnPropertyNames(obj), ...Object.getOwnPropertySymbols(obj)]) { acc.add(key); } return acc; }, new Set()); result.value = {}; for (const key of keys) { // merge objects with the same key const values = mergeableArgs.map(obj => obj[key]); const mergedValues = { value: undefined }; mergeCircular(mergedValues, values, cache, objects); result.value[key] = mergedValues.value; } break; } case MergeableType.Array: { result.value = []; const maxLength = mergeableArgs.reduce((acc, arr) => Math.max(acc, arr.length), 0); for (let i = 0; i < maxLength; i++) { // merge objects with the same index const values = mergeableArgs.map(obj => obj[i]).filter(obj => obj !== undefined); const mergedValues = { value: undefined }; mergeCircular(mergedValues, values, cache, objects); result.value[i] = mergedValues.value; } break; } case MergeableType.Map: { // find all keys const keys = mergeableArgs.reduce((acc, obj) => { for (const key of obj.keys()) { acc.add(key); } return acc; }, new Set()); result.value = new Map(); for (const key of keys) { // merge objects with the same key const values = mergeableArgs.map(obj => obj.get(key)); const mergedValues = { value: undefined }; mergeCircular(mergedValues, values, cache, objects); result.value.set(key, mergedValues.value); } break; } case MergeableType.Set: { result.value = mergeableArgs.reduce((acc, set) => { for (const value of set) { acc.add(value); } return acc; }, new Set()); break; } } } /** * Deep merges all arguments into a single object. Objects could have circular references. * @param args Array of objects to merge * @returns The deeply merged object. * @example * import merge from "merge-fast"; * merge([{ a: 1 }, { b: 2 }]); // { a: 1, b: 2 } * merge([{ a: 1 }, { a: 2 }]); // { a: 2 } */ function merge(args) { const result = { value: undefined }; mergeCircular(result, args, new Map(), new WeakMap()); return result.value; } export default merge;