deepMerge, deepDiff: Fixes for arrays, null-s, undefined-s and __delete* for non-objects

This commit is contained in:
Alexander Zinchuk 2024-04-19 13:37:46 +04:00
parent aac2ee85df
commit 9807dfb5d5
3 changed files with 50 additions and 42 deletions

View File

@ -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<T extends any>(value1: T, value2: T): Partial<T> | 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<T extends any>(value1: T, value2: T): Partial<T> | 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;
}

View File

@ -1,41 +1,32 @@
import { unique } from './iteratees';
export function deepMerge<T extends any>(value1: T, value2: Record<keyof T, any>): 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<T extends any>(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<T extends any>(value1: T, value2: Record<keyof T, any>
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;
}, {});
}

View File

@ -188,6 +188,10 @@ export function cloneDeep<T>(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;