From d3ff0b28fd090b3bbd91626703f2a890a6fd6f52 Mon Sep 17 00:00:00 2001 From: Jay Meistrich Date: Sat, 24 Aug 2024 17:45:22 -0700 Subject: [PATCH] fix setAtPath in "merge" mode was sometimes converting strings to objects --- src/helpers.ts | 31 ++++++++++++++++++++----------- tests/tests.test.ts | 45 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 11 deletions(-) diff --git a/src/helpers.ts b/src/helpers.ts index 8c1cfc65..d4a94f35 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -56,7 +56,7 @@ export function setAtPath( } else if (o[p] === undefined && value === undefined && i === path.length - 1) { // If setting undefined and the key is undefined, no need to initialize or set it return obj; - } else if (o[p] === undefined || o[p] === null) { + } else if (i < path.length - 1 && (o[p] === undefined || o[p] === null)) { const child = initializePathType(pathTypes[i]); if (isMap(o)) { o.set(p, child); @@ -219,21 +219,30 @@ export function deepMerge(target: T, ...sources: any[]): T { return sources[sources.length - 1]; } - const result: T = (isArray(target) ? [...target] : { ...target }) as T; + let result: T = (isArray(target) ? [...target] : { ...target }) as T; for (let i = 0; i < sources.length; i++) { const obj2 = sources[i]; - for (const key in obj2) { - if (hasOwnProperty.call(obj2, key)) { - if (obj2[key] instanceof Object && !isObservable(obj2[key]) && Object.keys(obj2[key]).length > 0) { - (result as any)[key] = deepMerge( - (result as any)[key] || (isArray((obj2 as any)[key]) ? [] : {}), - (obj2 as any)[key], - ); - } else { - (result as any)[key] = obj2[key]; + if (isObject(obj2) || isArray(obj2)) { + const objTarget = obj2 as Record; + for (const key in objTarget) { + if (hasOwnProperty.call(objTarget, key)) { + if ( + objTarget[key] instanceof Object && + !isObservable(objTarget[key]) && + Object.keys(objTarget[key]).length > 0 + ) { + (result as any)[key] = deepMerge( + (result as any)[key] || (isArray((objTarget as any)[key]) ? [] : {}), + (objTarget as any)[key], + ); + } else { + (result as any)[key] = objTarget[key]; + } } } + } else { + result = obj2; } } diff --git a/tests/tests.test.ts b/tests/tests.test.ts index 9ae13b89..db2f8f19 100644 --- a/tests/tests.test.ts +++ b/tests/tests.test.ts @@ -3351,6 +3351,51 @@ describe('setAtPath', () => { expect(Object.keys(res)).toEqual([]); expect(res).toEqual({}); }); + test('Set on empty object', () => { + const value = {}; + + const res = setAtPath(value, ['key', 'status'], ['object', 'object'], 'Completed'); + + expect(res).toEqual({ key: { status: 'Completed' } }); + }); + test('Set with merge on empty object', () => { + const value = {}; + + const res = setAtPath(value, ['key', 'status'], ['object', 'object'], 'Completed', 'merge'); + + expect(res).toEqual({ key: { status: 'Completed' } }); + }); + test('Set with merge on array', () => { + const value = { + arr: [{ id: 1 }, { id: 2 }, { id: 3 }], + }; + + const res = setAtPath(value, ['arr', '1', 'id'], ['object', 'object', 'object'], 22, 'merge'); + + expect(res).toEqual({ arr: [{ id: 1 }, { id: 22 }, { id: 3 }] }); + }); + test('Set on object with existing key', () => { + const value = { + key: { + sessionId: 'zz', + }, + }; + + const res = setAtPath(value, ['key', 'status'], ['object', 'object'], 'Completed'); + + expect(res).toEqual({ key: { sessionId: 'zz', status: 'Completed' } }); + }); + test('Set with merge on object with existing key', () => { + const value = { + key: { + sessionId: 'zz', + }, + }; + + const res = setAtPath(value, ['key', 'status'], ['object', 'object'], 'Completed', 'merge'); + + expect(res).toEqual({ key: { sessionId: 'zz', status: 'Completed' } }); + }); }); describe('new computed', () => { test('new computed basic', () => {