From 9807dfb5d56cbbba230faa2c5b87484097b14aac Mon Sep 17 00:00:00 2001 From: Alexander Zinchuk Date: Fri, 19 Apr 2024 13:37:46 +0400 Subject: [PATCH] `deepMerge`, `deepDiff`: Fixes for arrays, `null`-s, `undefined`-s and `__delete*` for non-objects --- src/util/deepDiff.ts | 32 +++++++++++-------------- src/util/deepMerge.ts | 56 ++++++++++++++++++++++++------------------- src/util/iteratees.ts | 4 ++++ 3 files changed, 50 insertions(+), 42 deletions(-) diff --git a/src/util/deepDiff.ts b/src/util/deepDiff.ts index e8ad7c6ec..d34d60f90 100644 --- a/src/util/deepDiff.ts +++ b/src/util/deepDiff.ts @@ -1,28 +1,26 @@ -import { unique } from './iteratees'; +import { isLiteralObject, unique } from './iteratees'; const EQUAL = Symbol('EQUAL'); const DELETE = { __delete: true }; const DELETE_ALL_CHILDREN = { __deleteAllChildren: true }; export function deepDiff(value1: T, value2: T): Partial | typeof EQUAL | typeof DELETE_ALL_CHILDREN { - const type1 = typeof value1; - const type2 = typeof value2; - if (value1 === value2) { return EQUAL; } + const type1 = typeof value1; + const type2 = typeof value2; + if (type1 !== type2) { return value2; } - if (type2 !== 'object') { - return value2; + if (Array.isArray(value1) && Array.isArray(value2) && areSortedArraysDeepEqual(value1, value2)) { + return EQUAL; } - if (Array.isArray(value1) && Array.isArray(value2)) { - if (areSortedArraysDeepEqual(value1, value2)) return EQUAL; - + if (!isLiteralObject(value1) || !isLiteralObject(value2)) { return value2; } @@ -38,22 +36,20 @@ export function deepDiff(value1: T, value2: T): Partial | type const allKeys = unique(keys1.concat(keys2)); const diff = allKeys.reduce((acc: any, key) => { - if (object1[key] === object2[key]) { - return acc; - } + const subValue1 = object1[key]; + const subValue2 = object2[key]; - const o1has = object1.hasOwnProperty(key); - const o2has = object2.hasOwnProperty(key); - if (!o2has) { + if (!object2.hasOwnProperty(key)) { acc[key] = DELETE; return acc; } - if (!o1has && o2has) { - acc[key] = object2[key]; + + if (!object1!.hasOwnProperty(key)) { + acc[key] = subValue2; return acc; } - const subDiff = deepDiff(object1[key], object2[key]); + const subDiff = deepDiff(subValue1, subValue2); if (subDiff !== EQUAL) { acc[key] = subDiff; } diff --git a/src/util/deepMerge.ts b/src/util/deepMerge.ts index 0219c92be..3fc93e3d8 100644 --- a/src/util/deepMerge.ts +++ b/src/util/deepMerge.ts @@ -1,41 +1,32 @@ -import { unique } from './iteratees'; - -export function deepMerge(value1: T, value2: Record): T { - const type1 = typeof value1; - const type2 = typeof value2; - if (type1 !== 'object') { - return value2 as T; - } - - if (Array.isArray(value2)) { - return value2 as T; - } - - if (type1 !== type2) { - return value2 as T; - } +import { isLiteralObject, unique } from './iteratees'; +export function deepMerge(value1: T, value2: T): T { if (value1 === value2) { - return value2 as T; + return value2; } - const object1 = value1 as AnyLiteral; - const object2 = value2 as AnyLiteral; + if (!isLiteralObject(value2)) { + return value2; + } + + if (!isLiteralObject(value1)) { + return reduceDiff(value2) as T; + } // eslint-disable-next-line no-underscore-dangle - if (object2.__deleteAllChildren) { + if (value2.__deleteAllChildren) { return {} as T; } - const allKeys = unique(Object.keys(object1).concat(Object.keys(object2))); + const allKeys = unique(Object.keys(value1).concat(Object.keys(value2))); return allKeys.reduce((acc: AnyLiteral, key) => { - const oldValue = object1[key]; + const oldValue = value1[key]; - if (!(key in object2)) { + if (!value2.hasOwnProperty(key)) { acc[key] = oldValue; } else { - const newValue = object2[key]; + const newValue = value2[key]; // eslint-disable-next-line no-underscore-dangle if (!newValue?.__delete) { acc[key] = deepMerge(oldValue, newValue); @@ -45,3 +36,20 @@ export function deepMerge(value1: T, value2: Record return acc; }, {}) as T; } + +function reduceDiff(diff: AnyLiteral) { + // eslint-disable-next-line no-underscore-dangle + if (diff.__deleteAllChildren) { + return {}; + } + + return Object.entries(diff).reduce((acc: AnyLiteral, [key, value]) => { + // eslint-disable-next-line no-underscore-dangle + if (!value?.__delete) { + // eslint-disable-next-line no-null/no-null + acc[key] = isLiteralObject(value) ? reduceDiff(value) : value; + } + + return acc; + }, {}); +} diff --git a/src/util/iteratees.ts b/src/util/iteratees.ts index b63fd5620..b3ec7bd56 100644 --- a/src/util/iteratees.ts +++ b/src/util/iteratees.ts @@ -188,6 +188,10 @@ export function cloneDeep(value: T): T { }, {} as T); } +export function isLiteralObject(value: any): value is AnyLiteral { + return isObject(value) && !Array.isArray(value); +} + function isObject(value: any): value is object { // eslint-disable-next-line no-null/no-null return typeof value === 'object' && value !== null;