Skip to content

Commit

Permalink
Allow custom array merging strategies
Browse files Browse the repository at this point in the history
Fixes #14, fixes #20, fixes #21, fixes #22, fixes #24, fixes #32
  • Loading branch information
TehShrike committed Sep 29, 2016
1 parent 242ea0f commit d34ab12
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 28 deletions.
11 changes: 9 additions & 2 deletions README.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
=======
Expand Down
64 changes: 38 additions & 26 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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

}));
44 changes: 44 additions & 0 deletions test/custom-array-merge.js
Original file line number Diff line number Diff line change
@@ -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()
})

0 comments on commit d34ab12

Please sign in to comment.