/** * Forked from fast-deep-equals to avoid comparing constructors as it breaks comparing model with raw data. Addition it * adds ability to ignore props */ export function isDeepEquals(a: unknown, b: unknown, ignoreProps?: string[]) { if (a === b) return true if (a && b && typeof a == 'object' && typeof b == 'object') { //if (a.constructor !== b.constructor) return false var length, i, keys if (Array.isArray(a)) { if (!Array.isArray(b)) return false length = a.length if (length != b.length) return false for (i = length; i-- !== 0; ) if (!isDeepEquals(a[i], b[i], ignoreProps)) return false return true } if (a instanceof RegExp && b instanceof RegExp) return a.source === b.source && a.flags === b.flags if (a.valueOf !== Object.prototype.valueOf) return a.valueOf() === b.valueOf() if (a.toString !== Object.prototype.toString) return a.toString() === b.toString() keys = Object.keys(a) as (keyof typeof a)[] length = keys.length if (length !== Object.keys(b).length) return false for (i = length; i-- !== 0; ) if (!Object.prototype.hasOwnProperty.call(b, keys[i])) return false for (i = length; i-- !== 0; ) { var key = keys[i] if (ignoreProps?.includes(key)) continue if (!isDeepEquals(a[key], b[key], ignoreProps)) return false } return true } // true if both NaN, false otherwise return a !== a && b !== b } export const objectKeysShallowCompare = (a: any, b: any) => Object.keys(a).every((key) => Object.keys(b).includes(key)) && Object.keys(b).every((key) => Object.keys(a).includes(key)) interface IObject { [key: string]: any } type TUnionToIntersection = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never // istanbul ignore next export const isObject = (obj: any) => { if (typeof obj === 'object' && obj !== null) { if (typeof Object.getPrototypeOf === 'function') { const prototype = Object.getPrototypeOf(obj) return prototype === Object.prototype || prototype === null } return Object.prototype.toString.call(obj) === '[object Object]' } return false } /** Check that every item in an array is an object */ const isObjectArray = (array: any[]) => { return array.every((item) => isObject(item)) } /** Forked from ts-deepmerge. Altered to serve double duty as cloneDeep and assignDeep */ const merge = ( arrayStrategy: 'combine' | 'mix' | 'overwrite' = 'overwrite', alwaysClone = false, ...objects: T ): TUnionToIntersection => objects.slice(1).reduce((result, current) => { // handle top level arrays if (Array.isArray(current)) { return current.map((value, i) => merge(arrayStrategy, alwaysClone, value, result?.[i])) } Object.keys(current || {}).forEach((key) => { if (current[key] === DELETE_PROPERTY) { delete result[key] } else if (Array.isArray(current[key]) && Array.isArray(result[key])) { if (arrayStrategy === 'combine') result[key] = Array.from(new Set(((result[key] as unknown[]) || []).concat(current[key] || []))) else if (arrayStrategy === 'overwrite') { result[key] = current[key] } // if strategy is mix and both arrays are object arrays, combine them else if (isObjectArray(result[key]) && isObjectArray(current[key])) result[key] = merge(arrayStrategy, alwaysClone, current[key], result[key]) if (Array.isArray(result[key]) && alwaysClone) result[key] = result[key].map(cloneDeep) } else if (arrayStrategy !== 'mix' && (Array.isArray(current[key]) || Array.isArray(result[key]))) { result[key] = arrayStrategy === 'combine' ? Array.from(new Set(((result[key] as unknown[]) || []).concat(current[key] || []))) : current[key] if (Array.isArray(result[key]) && alwaysClone) result[key] = result[key].map(cloneDeep) } else if ((isObject(result[key]) || result[key] == null) && isObject(current[key])) { result[key] = merge( arrayStrategy, alwaysClone, {}, (result[key] || {}) as IObject, (current[key] || {}) as IObject ) } else if (arrayStrategy !== 'mix' || !isObject(result[key])) { result[key] = current[key] } }) return result }, objects[0] || {}) as any /** Recursively merge objects into a new object. Arrays are overwritten */ export function mergeDeep(...objects: T): TUnionToIntersection { return merge('overwrite', true, {}, ...objects) } /** Recursively merge objects into a new object. Combine arrays */ export function combineDeep(...objects: T): TUnionToIntersection { return merge('combine', true, {}, ...objects) } /* Recursively merge objects into a new object, combine arrays and merges objects in arrays by index */ export function mixDeep(...objects: T) { return merge('mix', true, {}, ...objects) } /** MUTATION: Recursively merge objects into the first one as a target */ export function assignDeep(...objects: T): TUnionToIntersection { return merge('overwrite', false, ...objects) } /** Recursively clone object */ export function cloneDeep(object: T): T { if (object == null || typeof object != 'object') { return object } if (Array.isArray(object)) { return object.map(cloneDeep) as T } return merge('overwrite', true, {}, object) as T } export const DELETE_PROPERTY = Symbol('DELETE_PROPERTY')