Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ deepmerge(options)
- `all` (`boolean`, optional) - makes deepmerge accept and merge any number of passed objects, default is false
- `mergeArray` (`function`, optional) - provide a function, which returns a function to add custom array merging function
- `cloneProtoObject` (`function`, optional) - provide a function, which must return a clone of the object with the prototype of the object
- `isMergeableObject` (`function`, optional) - provide a function, which must return true if the object should be merged, default is `isMergeableObject` from this module

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

#### isMergeableObject

By default, `@fastify/deepmerge` merges all objects except native `Date` and `RegExp`-Objects. If you want to exclude certain objects from being merged, you can provide a custom function to the `isMergeableObject` option.

The default function is exported by this module as `isMergeableObject`.

Following example shows how to extend the default function to exclude globally defined `FormData`-Objects from being identified as mergeable objects.

```js
const { isMergeableObject: defaultIsMergeableObject } = require('@fastify/deepmerge')


function customIsMergeableObject (source) {
return defaultIsMergeableObject(source) && !(source instanceof FormData)
}

const deepmergeWithCustomMergeableObject = require('@fastify/deepmerge')({
isMergeableObject: customIsMergeableObject
})
```

## Benchmarks

The benchmarks are available in the benchmark folder.
Expand Down
22 changes: 14 additions & 8 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@

const JSON_PROTO = Object.getPrototypeOf({})

function defaultIsMergeableObjectFactory () {
return function defaultIsMergeableObject (value) {
return typeof value === 'object' && value !== null && !(value instanceof RegExp) && !(value instanceof Date)
}
}

function deepmergeConstructor (options) {
function isNotPrototypeKey (value) {
return (
Expand Down Expand Up @@ -73,18 +79,14 @@ function deepmergeConstructor (options) {
? options.cloneProtoObject
: undefined

function isMergeableObject (value) {
return typeof value === 'object' && value !== null && !(value instanceof RegExp) && !(value instanceof Date)
}
const isMergeableObject = typeof options?.isMergeableObject === 'function'
? options.isMergeableObject
: defaultIsMergeableObjectFactory()

function isPrimitive (value) {
return typeof value !== 'object' || value === null
}

const isPrimitiveOrBuiltIn = typeof Buffer !== 'undefined'
? (value) => typeof value !== 'object' || value === null || value instanceof RegExp || value instanceof Date || value instanceof Buffer
: (value) => typeof value !== 'object' || value === null || value instanceof RegExp || value instanceof Date

const mergeArray = options && typeof options.mergeArray === 'function'
? options.mergeArray({ clone, deepmerge: _deepmerge, getKeys, isMergeableObject })
: concatArrays
Expand Down Expand Up @@ -134,7 +136,7 @@ function deepmergeConstructor (options) {

if (isPrimitive(source)) {
return source
} else if (isPrimitiveOrBuiltIn(target)) {
} else if (!isMergeableObject(target)) {
return clone(source)
} else if (sourceIsArray && targetIsArray) {
return mergeArray(target, source)
Expand Down Expand Up @@ -169,3 +171,7 @@ function deepmergeConstructor (options) {
module.exports = deepmergeConstructor
module.exports.default = deepmergeConstructor
module.exports.deepmerge = deepmergeConstructor

Object.defineProperty(module.exports, 'isMergeableObject', {
get: defaultIsMergeableObjectFactory
})
68 changes: 68 additions & 0 deletions test/is-mergeable-object.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
'use strict'

// based on https://github.com/TehShrike/deepmerge/tree/3c39fb376158fa3cfc75250cfc4414064a90f582/test
// MIT License
// Copyright (c) 2012 - 2022 James Halliday, Josh Duff, and other contributors of deepmerge

const deepmerge = require('../index')
const test = require('tape').test

test('custom isMergeableObject', { skip: typeof FormData === 'undefined' }, function (t) {
function customIsMergeableObject (value) {
return (
typeof value === 'object' &&
value !== null &&
!(value instanceof RegExp) &&
!(value instanceof Date) &&
!(value instanceof FormData)
)
}

const merge = deepmerge({
isMergeableObject: customIsMergeableObject,
})
const destination = {
someArray: [1, 2],
someObject: new FormData()
}

const formdata = new FormData()
const source = {
someObject: formdata,
}

const actual = merge(destination, source)
const expected = {
someArray: [1, 2],
someObject: formdata
}

t.deepEqual(actual, expected)
t.end()
})

test('isMergeableObject is not mutable /1', function (t) {
t.plan(1)
try {
deepmerge.isMergeableObject = function (value) {
return false
}
} catch (e) {
t.pass('deepmerge.isMergeableObject is not mutable')
}

t.end()
})

test('isMergeableObject is not mutable /1', function (t) {
try {
Object.defineProperty(deepmerge, 'isMergeableObject', {
value: function (value) {
return false
}
})
} catch (e) {
t.pass('deepmerge.isMergeableObject is not mutable')
}
t.end()
})
3 changes: 3 additions & 0 deletions types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ type MergeArrayFn = (options: MergeArrayFnOptions) => (target: any[], source: an

interface Options {
mergeArray?: MergeArrayFn;
isMergeableObject?: (value: any) => boolean;
symbols?: boolean;
all?: boolean;
}
Expand All @@ -75,6 +76,8 @@ declare namespace deepmerge {
export { Options, DeepMergeFn, DeepMergeAllFn }
export const deepmerge: DeepmergeConstructor
export { deepmerge as default }

export const isMergeableObject: (value: any) => boolean
}

declare function deepmerge (options: Options & { all: true }): DeepMergeAllFn
Expand Down
8 changes: 8 additions & 0 deletions types/index.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,11 @@ deepmerge({
}
}
})
deepmerge({
isMergeableObject: function (value) {
return true
}
})

expectType<(value: any) => boolean>(deepmerge.isMergeableObject)
expectError(deepmerge.isMergeableObject = function () { return false })