Skip to content

Commit a55ac2e

Browse files
UzlopakFdawgs
andauthored
feat: make isMergeableObject configurable (#68)
* feat: make isMergeableObject configurable * skip test * lint * document * Update README.md Signed-off-by: Aras Abbasi <[email protected]> * Update README.md Co-authored-by: Frazer Smith <[email protected]> Signed-off-by: Aras Abbasi <[email protected]> --------- Signed-off-by: Aras Abbasi <[email protected]> Co-authored-by: Frazer Smith <[email protected]>
1 parent df6f4e1 commit a55ac2e

File tree

5 files changed

+115
-8
lines changed

5 files changed

+115
-8
lines changed

README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ deepmerge(options)
2727
- `all` (`boolean`, optional) - makes deepmerge accept and merge any number of passed objects, default is false
2828
- `mergeArray` (`function`, optional) - provide a function, which returns a function to add custom array merging function
2929
- `cloneProtoObject` (`function`, optional) - provide a function, which must return a clone of the object with the prototype of the object
30+
- `isMergeableObject` (`function`, optional) - provide a function, which must return true if the object should be merged, default is `isMergeableObject` from this module
3031

3132
```js
3233
const deepmerge = require('@fastify/deepmerge')()
@@ -133,6 +134,27 @@ const result = deepmergeByReference({}, { stream: process.stdout })
133134
console.log(result) // { stream: <ref *1> WriteStream }
134135
```
135136

137+
#### isMergeableObject
138+
139+
By default, `@fastify/deepmerge` merges all objects except native `Date` and `RegExp` objects. To exclude certain objects from being merged, you can provide a custom function to the `isMergeableObject` option.
140+
141+
The default function is exported by this module as `isMergeableObject`.
142+
143+
The following example shows how to extend the default function to exclude globally defined `FormData` objects from being identified as mergeable objects.
144+
145+
```js
146+
const { isMergeableObject: defaultIsMergeableObject } = require('@fastify/deepmerge')
147+
148+
149+
function customIsMergeableObject (source) {
150+
return defaultIsMergeableObject(source) && !(source instanceof FormData)
151+
}
152+
153+
const deepmergeWithCustomMergeableObject = require('@fastify/deepmerge')({
154+
isMergeableObject: customIsMergeableObject
155+
})
156+
```
157+
136158
## Benchmarks
137159

138160
The benchmarks are available in the benchmark folder.

index.js

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@
66

77
const JSON_PROTO = Object.getPrototypeOf({})
88

9+
function defaultIsMergeableObjectFactory () {
10+
return function defaultIsMergeableObject (value) {
11+
return typeof value === 'object' && value !== null && !(value instanceof RegExp) && !(value instanceof Date)
12+
}
13+
}
14+
915
function deepmergeConstructor (options) {
1016
function isNotPrototypeKey (value) {
1117
return (
@@ -73,18 +79,14 @@ function deepmergeConstructor (options) {
7379
? options.cloneProtoObject
7480
: undefined
7581

76-
function isMergeableObject (value) {
77-
return typeof value === 'object' && value !== null && !(value instanceof RegExp) && !(value instanceof Date)
78-
}
82+
const isMergeableObject = typeof options?.isMergeableObject === 'function'
83+
? options.isMergeableObject
84+
: defaultIsMergeableObjectFactory()
7985

8086
function isPrimitive (value) {
8187
return typeof value !== 'object' || value === null
8288
}
8389

84-
const isPrimitiveOrBuiltIn = typeof Buffer !== 'undefined'
85-
? (value) => typeof value !== 'object' || value === null || value instanceof RegExp || value instanceof Date || value instanceof Buffer
86-
: (value) => typeof value !== 'object' || value === null || value instanceof RegExp || value instanceof Date
87-
8890
const mergeArray = options && typeof options.mergeArray === 'function'
8991
? options.mergeArray({ clone, deepmerge: _deepmerge, getKeys, isMergeableObject })
9092
: concatArrays
@@ -134,7 +136,7 @@ function deepmergeConstructor (options) {
134136

135137
if (isPrimitive(source)) {
136138
return source
137-
} else if (isPrimitiveOrBuiltIn(target)) {
139+
} else if (!isMergeableObject(target)) {
138140
return clone(source)
139141
} else if (sourceIsArray && targetIsArray) {
140142
return mergeArray(target, source)
@@ -169,3 +171,7 @@ function deepmergeConstructor (options) {
169171
module.exports = deepmergeConstructor
170172
module.exports.default = deepmergeConstructor
171173
module.exports.deepmerge = deepmergeConstructor
174+
175+
Object.defineProperty(module.exports, 'isMergeableObject', {
176+
get: defaultIsMergeableObjectFactory
177+
})

test/is-mergeable-object.test.js

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
'use strict'
2+
3+
// based on https://github.com/TehShrike/deepmerge/tree/3c39fb376158fa3cfc75250cfc4414064a90f582/test
4+
// MIT License
5+
// Copyright (c) 2012 - 2022 James Halliday, Josh Duff, and other contributors of deepmerge
6+
7+
const deepmerge = require('../index')
8+
const test = require('tape').test
9+
10+
test('custom isMergeableObject', { skip: typeof FormData === 'undefined' }, function (t) {
11+
function customIsMergeableObject (value) {
12+
return (
13+
typeof value === 'object' &&
14+
value !== null &&
15+
!(value instanceof RegExp) &&
16+
!(value instanceof Date) &&
17+
!(value instanceof FormData)
18+
)
19+
}
20+
21+
const merge = deepmerge({
22+
isMergeableObject: customIsMergeableObject,
23+
})
24+
const destination = {
25+
someArray: [1, 2],
26+
someObject: new FormData()
27+
}
28+
29+
const formdata = new FormData()
30+
const source = {
31+
someObject: formdata,
32+
}
33+
34+
const actual = merge(destination, source)
35+
const expected = {
36+
someArray: [1, 2],
37+
someObject: formdata
38+
}
39+
40+
t.deepEqual(actual, expected)
41+
t.end()
42+
})
43+
44+
test('isMergeableObject is not mutable /1', function (t) {
45+
t.plan(1)
46+
try {
47+
deepmerge.isMergeableObject = function (value) {
48+
return false
49+
}
50+
} catch (e) {
51+
t.pass('deepmerge.isMergeableObject is not mutable')
52+
}
53+
54+
t.end()
55+
})
56+
57+
test('isMergeableObject is not mutable /1', function (t) {
58+
try {
59+
Object.defineProperty(deepmerge, 'isMergeableObject', {
60+
value: function (value) {
61+
return false
62+
}
63+
})
64+
} catch (e) {
65+
t.pass('deepmerge.isMergeableObject is not mutable')
66+
}
67+
t.end()
68+
})

types/index.d.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ type MergeArrayFn = (options: MergeArrayFnOptions) => (target: any[], source: an
7373
interface Options {
7474
cloneProtoObject?: CloneProtoObjectFn;
7575
mergeArray?: MergeArrayFn;
76+
isMergeableObject?: (value: any) => boolean;
7677
symbols?: boolean;
7778
all?: boolean;
7879
}
@@ -83,6 +84,8 @@ declare namespace deepmerge {
8384
export { Options, DeepMergeFn, DeepMergeAllFn }
8485
export const deepmerge: DeepmergeConstructor
8586
export { deepmerge as default }
87+
88+
export const isMergeableObject: (value: any) => boolean
8689
}
8790

8891
declare function deepmerge (options: Options & { all: true }): DeepMergeAllFn

types/index.test-d.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,3 +76,11 @@ deepmerge({
7676
}
7777
}
7878
})
79+
deepmerge({
80+
isMergeableObject: function (value) {
81+
return true
82+
}
83+
})
84+
85+
expectType<(value: any) => boolean>(deepmerge.isMergeableObject)
86+
expectError(deepmerge.isMergeableObject = function () { return false })

0 commit comments

Comments
 (0)