Skip to content

Commit 96d3251

Browse files
committed
fix(compat/merge): use isArrayLikeObject to match lodash merge behavior
1 parent 335011b commit 96d3251

File tree

3 files changed

+29
-2
lines changed

3 files changed

+29
-2
lines changed

src/compat/object/merge.spec.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,14 @@ describe('merge', () => {
370370
expect(actual).toEqual({ a: ['x', 'y', 'z'] });
371371
});
372372

373+
it('should not preserve object properties when nested object is replaced by array', () => {
374+
const actual = merge({ x: { a: 2 } }, { x: ['1'] });
375+
376+
expect(actual.x).toEqual(['1']);
377+
expect(Object.keys(actual.x)).toEqual(['0']);
378+
expect((actual.x as any).a).toBeUndefined();
379+
});
380+
373381
it('should match the type of lodash', () => {
374382
expectTypeOf(merge).toEqualTypeOf<typeof mergeLodash>();
375383
});

src/compat/object/mergeWith.spec.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,4 +228,12 @@ describe('mergeWith', () => {
228228
result = mergeWith({ a: 1 }, { a: undefined }, noop);
229229
expect(result.a).toBe(1);
230230
});
231+
232+
it('should not preserve object properties when nested object is replaced by array', () => {
233+
const actual = mergeWith({ x: { a: 2 } }, { x: ['1'] }, noop);
234+
235+
expect(actual.x).toEqual(['1']);
236+
expect(Object.keys(actual.x)).toEqual(['0']);
237+
expect((actual.x as any).a).toBeUndefined();
238+
});
231239
});

src/compat/object/mergeWith.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { clone } from '../../object/clone.ts';
44
import { isPrimitive } from '../../predicate/isPrimitive.ts';
55
import { getSymbols } from '../_internal/getSymbols.ts';
66
import { isArguments } from '../predicate/isArguments.ts';
7+
import { isArrayLikeObject } from '../predicate/isArrayLikeObject.ts';
78
import { isObjectLike } from '../predicate/isObjectLike.ts';
89
import { isPlainObject } from '../predicate/isPlainObject.ts';
910
import { isTypedArray } from '../predicate/isTypedArray.ts';
@@ -292,15 +293,25 @@ function mergeWithDeep(
292293
}
293294

294295
if (Array.isArray(sourceValue)) {
295-
if (typeof targetValue === 'object' && targetValue != null) {
296+
if (Array.isArray(targetValue)) {
296297
const cloned: any = [];
298+
// for custom properties on arrays
297299
const targetKeys = Reflect.ownKeys(targetValue);
298300

299301
for (let i = 0; i < targetKeys.length; i++) {
300-
const targetKey = targetKeys[i];
302+
const targetKey = targetKeys[i] as keyof typeof targetValue;
301303
cloned[targetKey] = targetValue[targetKey];
302304
}
303305

306+
targetValue = cloned;
307+
} else if (isArrayLikeObject(targetValue)) {
308+
// array-like object: only copy numeric indices
309+
const cloned: any = [];
310+
311+
for (let i = 0; i < targetValue.length; i++) {
312+
cloned[i] = targetValue[i];
313+
}
314+
304315
targetValue = cloned;
305316
} else {
306317
targetValue = [];

0 commit comments

Comments
 (0)