Skip to content

Commit

Permalink
Merge pull request #225 from TehShrike/208-dont-always-clone-over-des…
Browse files Browse the repository at this point in the history
…tination
  • Loading branch information
TehShrike authored Jan 21, 2021
2 parents caa79e2 + 0d6567c commit d51d92d
Show file tree
Hide file tree
Showing 4 changed files with 50 additions and 16 deletions.
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
- Breaking: by default, only [plain objects](https://github.com/sindresorhus/is-plain-obj/#is-plain-obj-) will have their properties merged, with all other values being copied to the target. [#152](https://github.com/TehShrike/deepmerge/issues/152)
- Breaking: the `isMergeableObject` option is renamed to `isMergeable` [#168](https://github.com/TehShrike/deepmerge/pull/168)
- Fixed: the options argument is no longer mutated (again) [#173](https://github.com/TehShrike/deepmerge/pull/173)
- Breaking+fixed: setting `clone` to `false` will cause values to be copied directly onto the destination object rather than cloning the destination and only mutating child properties. [#225](https://github.com/TehShrike/deepmerge/pull/225)

# [4.2.2](https://github.com/TehShrike/deepmerge/releases/tag/v4.2.2)

Expand Down
38 changes: 26 additions & 12 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const defaultIsMergeable = value => Array.isArray(value) || isPlainObj(value)
const emptyTarget = value => Array.isArray(value) ? [] : {}

const cloneUnlessOtherwiseSpecified = (value, options) => {
return (options.clone !== false && options.isMergeable(value))
return (options.clone && options.isMergeable(value))
? deepmerge(emptyTarget(value), value, options)
: value
}
Expand Down Expand Up @@ -48,7 +48,7 @@ const propertyIsUnsafe = (target, key) => {
}

const mergeObject = (target, source, options) => {
const destination = {}
const destination = options.clone ? emptyTarget(target) : target

if (options.isMergeable(target)) {
getKeys(target)
Expand All @@ -70,15 +70,18 @@ const mergeObject = (target, source, options) => {
return destination
}

const cloneOptionsWithDefault = inputOptions => ({
arrayMerge: defaultArrayMerge,
isMergeable: defaultIsMergeable,
clone: true,
...inputOptions,
// cloneUnlessOtherwiseSpecified is added to `options` so that custom arrayMerge()
// implementations can use it. The caller may not replace it.
cloneUnlessOtherwiseSpecified: cloneUnlessOtherwiseSpecified
})

const deepmerge = (target, source, inputOptions) => {
const options = {
arrayMerge: defaultArrayMerge,
isMergeable: defaultIsMergeable,
...inputOptions,
// cloneUnlessOtherwiseSpecified is added to `options` so that custom arrayMerge()
// implementations can use it. The caller may not replace it.
cloneUnlessOtherwiseSpecified: cloneUnlessOtherwiseSpecified
}
const options = cloneOptionsWithDefault(inputOptions)

const sourceIsArray = Array.isArray(source)
const targetIsArray = Array.isArray(target)
Expand All @@ -92,12 +95,23 @@ const deepmerge = (target, source, inputOptions) => {
return mergeObject(target, source, options)
}

deepmerge.all = (array, options) => {
deepmerge.all = (array, inputOptions) => {
if (!Array.isArray(array)) {
throw new Error(`first argument should be an array`)
}

return array.reduce((prev, next) => deepmerge(prev, next, options), {})
const options = cloneOptionsWithDefault(inputOptions)

if (array.length === 0) {
return {}
} else if (array.length === 1) {
const value = array[0]
return options.clone
? deepmerge(emptyTarget(value), value, options)
: value
}

return array.reduce((prev, next) => deepmerge(prev, next, options))
}

module.exports = deepmerge
5 changes: 1 addition & 4 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -249,12 +249,9 @@ result.pets // => ['Cat', 'Parrot', 'Dog']

### `clone`

*Deprecated.*

Defaults to `true`.

If `clone` is `false` then child objects will be copied directly instead of being cloned. This was the default behavior before version 2.x.

If `clone` is `false` then child properties will be copied directly onto targets without cloning the target.

# Testing

Expand Down
22 changes: 22 additions & 0 deletions test/merge.js
Original file line number Diff line number Diff line change
Expand Up @@ -685,3 +685,25 @@ test('should not mutate options', function(t) {
t.deepEqual(options, {})
t.end()
})

test('With clone: false, merge should not clone the target root', t => {
const destination = {}
const output = merge(destination, {
sup: true
}, { clone: false })

t.equal(destination, output)
t.end()
})

test('With clone: false, merge.all should not clone the target root', t => {
const destination = {}
const output = merge.all([
destination, {
sup: true
}
], { clone: false })

t.equal(destination, output)
t.end()
})

0 comments on commit d51d92d

Please sign in to comment.