UNPKG

merge-anything

Version:

Merge objects & other types recursively. A simple & small integration.

108 lines (107 loc) 4.04 kB
import { isPlainObject, isSymbol } from 'is-what'; import { concatArrays } from './extensions.js'; function assignProp(carry, key, newVal, originalObject) { const propType = {}.propertyIsEnumerable.call(originalObject, key) ? 'enumerable' : 'nonenumerable'; if (propType === 'enumerable') carry[key] = newVal; if (propType === 'nonenumerable') { Object.defineProperty(carry, key, { value: newVal, enumerable: false, writable: true, configurable: true, }); } } function mergeRecursively(origin, newComer, compareFn) { // always return newComer if its not an object if (!isPlainObject(newComer)) return newComer; // define newObject to merge all values upon let newObject = {}; if (isPlainObject(origin)) { const props = Object.getOwnPropertyNames(origin); const symbols = Object.getOwnPropertySymbols(origin); newObject = [...props, ...symbols].reduce((carry, key) => { const targetVal = origin[key]; if ((!isSymbol(key) && !Object.getOwnPropertyNames(newComer).includes(key)) || (isSymbol(key) && !Object.getOwnPropertySymbols(newComer).includes(key))) { assignProp(carry, key, targetVal, origin); } return carry; }, {}); } // newObject has all properties that newComer hasn't const props = Object.getOwnPropertyNames(newComer); const symbols = Object.getOwnPropertySymbols(newComer); const result = [...props, ...symbols].reduce((carry, key) => { // re-define the origin and newComer as targetVal and newVal let newVal = newComer[key]; const targetVal = isPlainObject(origin) ? origin[key] : undefined; // When newVal is an object do the merge recursively if (targetVal !== undefined && isPlainObject(newVal)) { newVal = mergeRecursively(targetVal, newVal, compareFn); } const propToAssign = compareFn ? compareFn(targetVal, newVal, key) : newVal; assignProp(carry, key, propToAssign, newComer); return carry; }, newObject); return result; } /** * Merge anything recursively. * Objects get merged, special objects (classes etc.) are re-assigned "as is". * Basic types overwrite objects or other basic types. */ export function merge(object, ...otherObjects) { return otherObjects.reduce((result, newComer) => { return mergeRecursively(result, newComer); }, object); } export function mergeAndCompare(compareFn, object, ...otherObjects) { return otherObjects.reduce((result, newComer) => { return mergeRecursively(result, newComer, compareFn); }, object); } export function mergeAndConcat(object, ...otherObjects) { return otherObjects.reduce((result, newComer) => { return mergeRecursively(result, newComer, concatArrays); }, object); } // import { Timestamp } from '../test/Timestamp' // type T1 = { date: Timestamp } // type T2 = [{ b: string[] }, { b: number[] }, { date: Timestamp }] // type TestT = Merge<T1, T2> // type A1 = { arr: string[] } // type A2 = { arr: number[] } // type A3 = { arr: boolean[] } // type TestA = Merge<A1, [A2, A3]> // interface I1 { // date: Timestamp // } // interface I2 { // date: Timestamp // } // const _a: I2 = { date: '' } as unknown as I2 // type TestI = Merge<I1, [I2]> // // ReturnType<(typeof merge)<I1, I2>> // const a = merge(_a, [_a]) // interface Arguments extends Record<string | number | symbol, unknown> { // key: string; // } // const aa1: Arguments = { key: "value1" } // const aa2: Arguments = { key: "value2" } // const aa = merge(a1, a2); // interface Barguments { // key: string // } // const ba1: Barguments = { key: 'value1' } // const ba2: Barguments = { key: 'value2' } // const ba = merge(ba1, ba2) // interface Carguments { // key: string // } // const ca = merge<Carguments, Carguments[]>({ key: 'value1' }, { key: 'value2' }) // type P = Pop<Carguments[]>