@tim-code/my-util 0.2.5 → 0.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/object.js +34 -0
- package/src/object.test.js +99 -1
package/package.json
CHANGED
package/src/object.js
CHANGED
|
@@ -55,3 +55,37 @@ export function like(template) {
|
|
|
55
55
|
return true
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Deeply merges one or more source objects into a target object.
|
|
61
|
+
* If the property values are objects, they are merged recursively.
|
|
62
|
+
* Non-object properties (including arrays) are directly assigned to the target.
|
|
63
|
+
* @param {Object} target The target object that will receive the merged properties.
|
|
64
|
+
* @param {...Object} sources The source objects whose properties will be merged into the target.
|
|
65
|
+
* @returns {Object} The target object with the merged properties from all source objects.
|
|
66
|
+
*/
|
|
67
|
+
export function deepMerge(target, ...sources) {
|
|
68
|
+
for (const source of sources) {
|
|
69
|
+
const keys = Object.keys(source)
|
|
70
|
+
for (const key of keys) {
|
|
71
|
+
if (!Object.hasOwn(source, key)) {
|
|
72
|
+
continue
|
|
73
|
+
}
|
|
74
|
+
const targetValue = target[key]
|
|
75
|
+
const sourceValue = source[key]
|
|
76
|
+
if (Array.isArray(sourceValue)) {
|
|
77
|
+
target[key] = sourceValue
|
|
78
|
+
} else if (
|
|
79
|
+
targetValue &&
|
|
80
|
+
typeof targetValue === "object" &&
|
|
81
|
+
sourceValue &&
|
|
82
|
+
typeof sourceValue === "object"
|
|
83
|
+
) {
|
|
84
|
+
deepMerge(targetValue, sourceValue)
|
|
85
|
+
} else {
|
|
86
|
+
target[key] = sourceValue
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return target
|
|
91
|
+
}
|
package/src/object.test.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* eslint-disable no-restricted-syntax */
|
|
2
2
|
import { jest } from "@jest/globals"
|
|
3
|
-
import { deleteUndefinedValues, like, mutateValues, via } from "./object.js"
|
|
3
|
+
import { deepMerge, deleteUndefinedValues, like, mutateValues, via } from "./object.js"
|
|
4
4
|
|
|
5
5
|
describe("mutateValues", () => {
|
|
6
6
|
it("mutates values in the object using the callback", () => {
|
|
@@ -159,3 +159,101 @@ describe("contains", () => {
|
|
|
159
159
|
expect(fn(obj)).toBe(false)
|
|
160
160
|
})
|
|
161
161
|
})
|
|
162
|
+
|
|
163
|
+
describe("deepMerge", () => {
|
|
164
|
+
it("merges flat objects", () => {
|
|
165
|
+
const target = { a: 1, b: 2 }
|
|
166
|
+
const source = { b: 3, c: 4 }
|
|
167
|
+
expect(deepMerge(target, source)).toEqual({ a: 1, b: 3, c: 4 })
|
|
168
|
+
expect(target).toEqual({ a: 1, b: 3, c: 4 })
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
it("deeply merges nested objects", () => {
|
|
172
|
+
const target = { a: { x: 1, y: 2 }, b: 2 }
|
|
173
|
+
const source = { a: { y: 3, z: 4 }, b: 5 }
|
|
174
|
+
expect(deepMerge(target, source)).toEqual({ a: { x: 1, y: 3, z: 4 }, b: 5 })
|
|
175
|
+
expect(target).toEqual({ a: { x: 1, y: 3, z: 4 }, b: 5 })
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
it("overwrites arrays instead of merging them", () => {
|
|
179
|
+
const target = { arr: [1, 2], a: 1 }
|
|
180
|
+
const source = { arr: [3, 4] }
|
|
181
|
+
expect(deepMerge(target, source)).toEqual({ arr: [3, 4], a: 1 })
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
it("merges multiple sources left-to-right", () => {
|
|
185
|
+
const target = { a: 1 }
|
|
186
|
+
const s1 = { b: 2 }
|
|
187
|
+
const s2 = { a: 3, c: 4 }
|
|
188
|
+
expect(deepMerge(target, s1, s2)).toEqual({ a: 3, b: 2, c: 4 })
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
it("does not merge non-object values, just assigns", () => {
|
|
192
|
+
const target = { a: { x: 1 } }
|
|
193
|
+
const source = { a: 2 }
|
|
194
|
+
expect(deepMerge(target, source)).toEqual({ a: 2 })
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
it("handles empty sources", () => {
|
|
198
|
+
const target = { a: 1 }
|
|
199
|
+
expect(deepMerge(target)).toEqual({ a: 1 })
|
|
200
|
+
expect(deepMerge(target, {})).toEqual({ a: 1 })
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
it("handles empty target", () => {
|
|
204
|
+
const target = {}
|
|
205
|
+
const source = { a: 1 }
|
|
206
|
+
expect(deepMerge(target, source)).toEqual({ a: 1 })
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
it("does not merge inherited properties from sources", () => {
|
|
210
|
+
const proto = { x: 1 }
|
|
211
|
+
const source = Object.create(proto)
|
|
212
|
+
source.a = 2
|
|
213
|
+
const target = {}
|
|
214
|
+
expect(deepMerge(target, source)).toEqual({ a: 2 })
|
|
215
|
+
expect("x" in target).toBe(false)
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
it("merges deeply with multiple sources", () => {
|
|
219
|
+
const target = { a: { x: 1 } }
|
|
220
|
+
const s1 = { a: { y: 2 } }
|
|
221
|
+
const s2 = { a: { z: 3 } }
|
|
222
|
+
expect(deepMerge(target, s1, s2)).toEqual({ a: { x: 1, y: 2, z: 3 } })
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
it("does not merge if source value is null", () => {
|
|
226
|
+
const target = { a: { x: 1 } }
|
|
227
|
+
const source = { a: null }
|
|
228
|
+
expect(deepMerge(target, source)).toEqual({ a: null })
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
it("does not merge if target value is null", () => {
|
|
232
|
+
const target = { a: null }
|
|
233
|
+
const source = { a: { x: 1 } }
|
|
234
|
+
expect(deepMerge(target, source)).toEqual({ a: { x: 1 } })
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
it("does not merge arrays deeply", () => {
|
|
238
|
+
const target = { a: [1, 2, 3] }
|
|
239
|
+
const source = { a: [4, 5] }
|
|
240
|
+
expect(deepMerge(target, source)).toEqual({ a: [4, 5] })
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
it("does not merge non-enumerable properties from source", () => {
|
|
244
|
+
const target = {}
|
|
245
|
+
const source = {}
|
|
246
|
+
Object.defineProperty(source, "hidden", {
|
|
247
|
+
value: 123,
|
|
248
|
+
enumerable: false,
|
|
249
|
+
})
|
|
250
|
+
expect(deepMerge(target, source)).toEqual({})
|
|
251
|
+
expect("hidden" in target).toBe(false)
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
it("returns the target object", () => {
|
|
255
|
+
const target = { a: 1 }
|
|
256
|
+
const result = deepMerge(target, { b: 2 })
|
|
257
|
+
expect(result).toBe(target)
|
|
258
|
+
})
|
|
259
|
+
})
|