Deep merging of Immutable structures with custom per-property merging strategy.
npm install immutable-custom-merge
import {fromJS} from 'immutable'
import merge from 'immutable-custom-merge'
const object1 = fromJS({
a: [1, 2, 3],
b: {
x: [1, 2, 3],
y: ['a', 'b'],
},
c: [1, 2, 3],
d: 4,
e: 5
})
const object2 = fromJS({
a: [3, 4],
b: {
x: [3, 4],
y: ['b', 'c'],
},
c: [3, 4],
d: 8,
e: 6
})
// defines how properties should be merged in case of collisions
const mergeSchema = {
a: 'append', // colliding iterables should be appended
b: {
'*': 'union', // 'union' function will be applied on every key under `b`
},
e: (a, b) => a + b // use custom defined function to merge values under this key
}
// merge two immutable Maps using the schema
const result = merge(object1, object2, mergeSchema)
Result of the above merging would be Immutable Map having this structure:
{
a: [1, 2, 3, 3, 4], // colliding array was appended
b: {
x: [1, 2, 3, 4], // union of items
y: ['a', 'b', 'c'] // union of items
},
c: [3, 4, 3], // no rule - default Immutable merge
d: 8, // no rule for property means later overrides former
e: 11 // custom function to solve collisions
}
immutable-custom-merge
exports just one function. It performs deep merge of val1
into val2
, using provided schema
to determine how to
merge specific keys if there's a collision.
val1
: required Immutable structure (most typically Map) to be merged with second argument. Beyond Maps it supports other Immutable types where deep merging makes sense.val2
: required Immutable structure to be merged intoval1
schema
: structured js object defining merge strategy for specific keys of input parameters. String with the name of one of pre-defined merge functions or custom merge function. Ifschema
is string or function, first two arguments doesn't need to be immutables. If schema is not supplied at all, it falls back to standard immutablemergeDeep
function. If schema is not supplied and first argument is non-immutable, it just returns second argument.
Basically, you need to specify merge function for certain (sub)keys of your input data that you want to merge differently from how native immutable merge function does it.
Schema bellow only defines special behaviour for sub-key messages
. Every other key
will be treated as it would be merged by native mergeDeep
function.
const mergeSchema = {
result: {
messages: 'append'
}
}
There are two kinds of merge functions that can be specified in the schema. Pre-defined and custom merge functions.
Merge function is called when key (for which it is defined in merge schema) is present in both val1
and val2
. Merging function receives values of this keys as it's arguments (first one is val1's
, second one is val2's
).
If you need to apply some merge function on every (sub)item, you can specify '*'
instead of a
key. This is quite usefull in situation when you store similar structured records
in Map-like objects (maybe you use normalizr
?):
const object1 = fromJS({
books: {
'111': {
id: '111',
author: '888'
},
'222': {
id: '222',
author: '999'
}
},
authors: {
...
}
})
You may want to merge book records into your store, but some sources may list a book with more authors so you might need to keep all of them. You would use this schema to merge in new data:
const mergeSchema = {
books: {
'*': {
author: 'union'
}
}
}
There are some pre-defined function to choose from:
Works with Immutable Iterables. Appends items of second parameter to the end of the first one
const object1 = fromJS({ a: [1, 2, 3] })
const object2 = fromJS({ a: [3, 4] })
const mergeSchema = { a: 'append' }
const result = merge(object1, object2, mergeSchema)
// result: { a: [1, 2, 3, 3, 4] }
Same as append but items are prepended
const object1 = fromJS({ a: [1, 2, 3] })
const object2 = fromJS({ a: [3, 4] })
const mergeSchema = { a: 'prepend' }
const result = merge(object1, object2, mergeSchema)
// result: { a: [3, 4, 1, 2, 3] }
Same as append but only items that are not already in the list are appended
const object1 = fromJS({ a: [1, 2, 3] })
const object2 = fromJS({ a: [3, 4] })
const mergeSchema = { a: 'union' }
const result = merge(object1, object2, mergeSchema)
// result: { a: [1, 2, 3, 4] }
Instead of using predefined function, you can supply your own. It will receive two arguments, conflicting values.
const object1 = fromJS({ a: 3 })
const object2 = fromJS({ a: 5 })
const mergeSchema = { a: (a, b) => a + b }
const result = merge(object1, object2, mergeSchema)
// result: { a: 8 }