Skip to content

Commit 6673209

Browse files
committed
fix: ignore prototype methods when using setData on objects
1 parent 4bc3baa commit 6673209

File tree

2 files changed

+130
-1
lines changed

2 files changed

+130
-1
lines changed

src/utils.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,25 @@ export function mergeGlobalProperties(
7272
export const isObject = (obj: unknown): obj is Record<string, any> =>
7373
!!obj && typeof obj === 'object'
7474

75+
function isClass(obj: unknown) {
76+
if (!(obj instanceof Object)) return
77+
78+
const isCtorClass =
79+
obj.constructor && obj.constructor.toString().substring(0, 5) === 'class'
80+
81+
if (!('prototype' in obj)) {
82+
return isCtorClass
83+
}
84+
85+
const prototype = obj.prototype as any
86+
const isPrototypeCtorClass =
87+
prototype.constructor &&
88+
prototype.constructor.toString &&
89+
prototype.constructor.toString().substring(0, 5) === 'class'
90+
91+
return isCtorClass || isPrototypeCtorClass
92+
}
93+
7594
// https://stackoverflow.com/a/48218209
7695
export const mergeDeep = (
7796
target: Record<string, unknown>,
@@ -80,8 +99,13 @@ export const mergeDeep = (
8099
if (!isObject(target) || !isObject(source)) {
81100
return source
82101
}
102+
83103
Object.keys(source)
84-
.concat(Object.getOwnPropertyNames(Object.getPrototypeOf(source) ?? {}))
104+
.concat(
105+
isClass(source)
106+
? Object.getOwnPropertyNames(Object.getPrototypeOf(source) ?? {})
107+
: Object.getOwnPropertyNames(source)
108+
)
85109
.forEach((key) => {
86110
const targetValue = target[key]
87111
const sourceValue = source[key]

tests/utils.spec.ts

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/**
2+
* @vitest-environment node
3+
*/
4+
5+
import { describe, expect, it } from 'vitest'
6+
import { mergeDeep } from '../src/utils'
7+
8+
describe('utils', () => {
9+
it('should be possible to replace a primitive value with another', () => {
10+
// ARRANGE
11+
const state = {
12+
foo: 'bar'
13+
}
14+
15+
// ACT
16+
const result = mergeDeep(state, {
17+
foo: true
18+
})
19+
20+
// ASSERT
21+
expect(result).toStrictEqual({
22+
foo: true
23+
})
24+
})
25+
26+
it('should be possible to merge a nested object', () => {
27+
// ARRANGE
28+
const state = {
29+
foo: {
30+
bar: 'baz'
31+
}
32+
}
33+
34+
// ACT
35+
const result = mergeDeep(state, {
36+
foo: {
37+
bar: 'bar',
38+
foo: 'foo'
39+
}
40+
})
41+
42+
// ASSERT
43+
expect(result).toStrictEqual({
44+
foo: {
45+
bar: 'bar',
46+
foo: 'foo'
47+
}
48+
})
49+
})
50+
51+
it('should be possible to replace an array', () => {
52+
// ARRANGE
53+
const state = {
54+
foo: []
55+
}
56+
57+
// ACT
58+
const result = mergeDeep(state, {
59+
foo: ['bar', 'baz']
60+
})
61+
62+
// ASSERT
63+
expect(result).toStrictEqual({
64+
foo: ['bar', 'baz']
65+
})
66+
})
67+
68+
it('should be possible to add additional key', () => {
69+
// ARRANGE
70+
const state = {
71+
foo: 'bar'
72+
}
73+
74+
// ACT
75+
const result = mergeDeep(state, {
76+
baz: 'foo'
77+
})
78+
79+
// ASSERT
80+
expect(result).toStrictEqual({
81+
foo: 'bar',
82+
baz: 'foo'
83+
})
84+
})
85+
86+
it('should result in the same value as before merging', () => {
87+
// ARRANGE
88+
const state = {
89+
foo: [],
90+
bar: []
91+
}
92+
93+
// ACT
94+
const result = mergeDeep(state, {
95+
foo: [],
96+
bar: []
97+
})
98+
99+
// ASSERT
100+
expect(result).toStrictEqual({
101+
foo: [],
102+
bar: []
103+
})
104+
})
105+
})

0 commit comments

Comments
 (0)