From 325939daa0ead1a8da68542bf561cdd63cf07aeb Mon Sep 17 00:00:00 2001 From: Josh Duff Date: Mon, 26 Sep 2016 21:44:54 -0500 Subject: [PATCH 1/2] Refactor: killing acronyms and simplifying "if" check --- index.js | 34 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/index.js b/index.js index fd8bade..4558d0b 100644 --- a/index.js +++ b/index.js @@ -16,42 +16,38 @@ function isMergeableObject(val) { && Object.prototype.toString.call(val) !== '[object Date]' } -return function deepmerge(target, src) { - var array = Array.isArray(src); - var dst = array ? [] : {}; +return function deepmerge(target, source) { + var array = Array.isArray(source); + var destination = array ? [] : {}; if (array) { target = target || []; - dst = dst.concat(target); - src.forEach(function(e, i) { - if (typeof dst[i] === 'undefined') { - dst[i] = e; + destination = destination.concat(target); + source.forEach(function(e, i) { + if (typeof destination[i] === 'undefined') { + destination[i] = e; } else if (isMergeableObject(e)) { - dst[i] = deepmerge(target[i], e); + destination[i] = deepmerge(target[i], e); } else if (target.indexOf(e) === -1) { - dst.push(e); + destination.push(e); } }); } else { if (isMergeableObject(target)) { Object.keys(target).forEach(function (key) { - dst[key] = target[key]; + destination[key] = target[key]; }) } - Object.keys(src).forEach(function (key) { - if (!isMergeableObject(src[key])) { - dst[key] = src[key]; + Object.keys(source).forEach(function (key) { + if (!isMergeableObject(source[key]) || !target[key]) { + destination[key] = source[key]; } else { - if (!target[key]) { - dst[key] = src[key]; - } else { - dst[key] = deepmerge(target[key], src[key]); - } + destination[key] = deepmerge(target[key], source[key]); } }); } - return dst; + return destination; } })); From 23910e7d24e297e8acbbe7ca1c1830e18176bca9 Mon Sep 17 00:00:00 2001 From: Josh Duff Date: Mon, 26 Sep 2016 22:18:50 -0500 Subject: [PATCH 2/2] Allow custom array merging strategies Fixes #14, fixes #20, fixes #21, fixes #22, fixes #24, fixes #32 --- README.markdown | 11 +++++-- index.js | 64 ++++++++++++++++++++++---------------- test/custom-array-merge.js | 44 ++++++++++++++++++++++++++ 3 files changed, 91 insertions(+), 28 deletions(-) create mode 100644 test/custom-array-merge.js diff --git a/README.markdown b/README.markdown index 0de708e..2de6c70 100644 --- a/README.markdown +++ b/README.markdown @@ -31,7 +31,7 @@ methods var merge = require('deepmerge') ``` -merge(x, y) +merge(x, y, [options]) ----------- Merge two objects `x` and `y` deeply, returning a new merged object with the @@ -42,7 +42,14 @@ If an element at the same key is present for both `x` and `y`, the value from The merge is immutable, so neither `x` nor `y` will be modified. -The merge will also merge arrays and array values. +The merge will also merge arrays and array values by default. However, there are nigh-infinite valid ways to merge arrays, and you may want to supply your own. You can do this by passing an `arrayMerge` function as an option. + +```js +function concatMerge(destinationArray, sourceArray, mergeOptions) { + return destinationArray.concat(sourceArray) +} +merge([1, 2, 3], [1, 2, 3], { arrayMerge: concatMerge }) // => [1, 2, 3, 1, 2, 3] +``` install ======= diff --git a/index.js b/index.js index 4558d0b..bb0a704 100644 --- a/index.js +++ b/index.js @@ -16,38 +16,50 @@ function isMergeableObject(val) { && Object.prototype.toString.call(val) !== '[object Date]' } -return function deepmerge(target, source) { +function defaultArrayMerge(target, source, optionsArgument) { + var destination = target.slice() + source.forEach(function(e, i) { + if (typeof destination[i] === 'undefined') { + destination[i] = e + } else if (isMergeableObject(e)) { + destination[i] = deepmerge(target[i], e, optionsArgument) + } else if (target.indexOf(e) === -1) { + destination.push(e) + } + }) + return destination +} + +function mergeObject(target, source, optionsArgument) { + var destination = {} + if (isMergeableObject(target)) { + Object.keys(target).forEach(function (key) { + destination[key] = target[key] + }) + } + Object.keys(source).forEach(function (key) { + if (!isMergeableObject(source[key]) || !target[key]) { + destination[key] = source[key] + } else { + destination[key] = deepmerge(target[key], source[key], optionsArgument) + } + }) + return destination +} + +function deepmerge(target, source, optionsArgument) { var array = Array.isArray(source); - var destination = array ? [] : {}; + var options = optionsArgument || { arrayMerge: defaultArrayMerge } + var arrayMerge = options.arrayMerge || defaultArrayMerge if (array) { target = target || []; - destination = destination.concat(target); - source.forEach(function(e, i) { - if (typeof destination[i] === 'undefined') { - destination[i] = e; - } else if (isMergeableObject(e)) { - destination[i] = deepmerge(target[i], e); - } else if (target.indexOf(e) === -1) { - destination.push(e); - } - }); + return arrayMerge(target, source, optionsArgument) } else { - if (isMergeableObject(target)) { - Object.keys(target).forEach(function (key) { - destination[key] = target[key]; - }) - } - Object.keys(source).forEach(function (key) { - if (!isMergeableObject(source[key]) || !target[key]) { - destination[key] = source[key]; - } else { - destination[key] = deepmerge(target[key], source[key]); - } - }); + return mergeObject(target, source, optionsArgument) } - - return destination; } +return deepmerge + })); diff --git a/test/custom-array-merge.js b/test/custom-array-merge.js new file mode 100644 index 0000000..f339c17 --- /dev/null +++ b/test/custom-array-merge.js @@ -0,0 +1,44 @@ +var merge = require('../') +var test = require('tap').test + +test('custom merge array', function(t) { + var mergeFunctionCalled = false + function concatMerge(target, source, options) { + t.notOk(mergeFunctionCalled) + mergeFunctionCalled = true + + t.deepEqual(target, [1, 2]) + t.deepEqual(source, [1, 2, 3]) + t.equal(options.arrayMerge, concatMerge) + + return target.concat(source) + } + const destination = { + someArray: [1, 2], + someObject: { what: 'yes' } + } + const source = { + someArray: [1, 2, 3] + } + + const actual = merge(destination, source, { arrayMerge: concatMerge }) + const expected = { + someArray: [1, 2, 1, 2, 3], + someObject: { what: 'yes' } + } + + t.ok(mergeFunctionCalled) + t.deepEqual(actual, expected) + t.end() +}) + +test('merge top-level arrays', function(t) { + function concatMerge(a, b) { + return a.concat(b) + } + var actual = merge([1, 2], [1, 2], { arrayMerge: concatMerge }) + var expected = [1, 2, 1, 2] + + t.deepEqual(actual, expected) + t.end() +})