Package Exports
- object-merge-advanced
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (object-merge-advanced) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
object-merge-advanced
Recursive, deep merge of anything (objects, arrays, strings or nested thereof), which weighs contents by type hierarchy to ensure the maximum content is retained
Table of Contents
Install
$ npm install --save object-merge-advanced
Purpose
It's like _.merge
, but it correctly merges different-type things and behaves well when it encounters nested things like parsed HTML (lots of nested arrays, objects and strings).
I was not happy with Lodash _.merge because it gets stuck when it encounters mismatching type values within plain objects. All I wanted is to merge two plain objects, retaining as much information as possible after the merging.
I was not happy with object-assign which doesn't care about what type is overwriting what type — it can merge value as String containing some text with Boolean false
, for example. object-assign
is good to merge default key set, but not to merge to objects containing precious content.
Merge these two:
// #1:
{
a: {
b: 'c',
d: ['e', 'f']
}
}
// and #2:
{
a: [
{
x: 'y'
}
]
}
Imagine, if we merged the identical keys of two objects judging their values by the hierarchy instead:
- non-empty array trumps all below
- non-empty plain object trumps all below
- non-empty string ...
- empty plain object ...
- empty array
- empty string
- number
- boolean
- null
- undefined doesn't trump anything
The idea is, we strive to retain as much info as possible after merging. For example, you'd be better off with a non-empty string that with an empty array or boolean.
That's what this library does
When object-merge-advanced
merges two objects, it will check the types of their key values:
- If a key exists only in one of the objects, it goes straight into the result object.
- If a key exists on both, we got a clash. Key's value will be chosen judging by its value's type:
- Arrays trump objects which trump strings which trump numbers which trump Booleans
- Non-empty array as value trumps any object or string as value
- Anything empty won't trump anything not empty
- If both keys have plain object values, they'll get recursively fed back into the library again
- Booleans will be merged using logical "OR"
- Arrays will be merged, and if there are objects within, those objects will be merged smartly, depending if their keysets are similar. If not, objects will be merged as separate array elements.
There are ten possible combinations: 10 types of first input (object #1) and ten types of second input (object #2): non-empty (full) object, empty object, non-empty array, empty array, non-empty string, empty string, number, boolean, undefined and null.
A Large number in the centre of a square shows which value prevails.
In the diagram above, the squares show which value wins, first object's (marked 01
) or second one's (marked 02
). In other words, do we assign second object's value onto first, or the opposite.
In certain cases, there are custom actions needed:
- passing value objects back into the main function recursively (when both values are plain objects),
- array merge paying extra attention to options object and array contents (special measures for objects within), or
- Boolean "and" composition (when both values are Boolean).
I challenge you to check test.js
unit tests to see this library in action.
In practice
In practice I needed this library to normalise JSON files — generate a "schema" object (a superset of all used keys) and fill any missing keys within all JSON files. Also, JSON files get their keys sorted. That library is used to keep us sane when using JSON to store content for email templates — it's enough to add one unique key in one JSON, and all other templates' content files get it added as well.
Use
var mergeAdvanced = require('object-merge-advanced')
API
mergeAdvanced(object1, object2)
API - Input
Input argument | Type | Obligatory? | Description |
---|---|---|---|
object1 |
Anything | yes | Normally an object literal, but array or string or whatever else will work too. Can be deeply nested. |
object2 |
Anything | yes | Another thing, normally an object, but can be array or something else. |
options |
Plain object | no | Optionally, pass all settings in a plain object, as a third argument |
Options object's key | Value | Default | Description |
---|---|---|---|
{ |
|||
mergeObjectsOnlyWhenKeysetMatches |
Boolean | true | Controls the merging of the objects within arrays. See below. |
} |
mergeObjectsOnlyWhenKeysetMatches
is an extra insurance from accidental merging two objects within arrays, where key sets are too different (both have at least one unique key).
For example:
Let's merge these two objects. Notice that each has a unique key (yyyy
and xxxx
in the object that sits within the first position of each array).
// #1
var obj1 = {
a: [
{
a: 'a',
b: 'b',
yyyy: 'yyyy'
}
]
}
var obj2 = {
a: [
{
xxxx: 'xxxx',
b: 'b',
c: 'c'
}
]
}
var res1 = mergeAdvanced(object1, object2)
console.log('res1 = ' + JSON.stringify(res1, null, 4))
// => {
// a: [
// {
// a: 'a',
// b: 'b',
// yyyy: 'yyyy'
// },
// {
// xxxx: 'xxxx',
// b: 'b',
// c: 'c'
// }
// ]
// }
but if you turn off the safeguard, { mergeObjectsOnlyWhenKeysetMatches: false }
each object within an array is merged no matter their differences in the keysets:
var res2 = mergeAdvanced(object1, object2, { mergeObjectsOnlyWhenKeysetMatches: false })
console.log('res2 = ' + JSON.stringify(res2, null, 4))
// => {
// a: [
// {
// a: 'a',
// b: 'b',
// yyyy: 'yyyy',
// xxxx: 'xxxx',
// c: 'c'
// }
// ]
// }
API - Output
A merged thing is returned. It's probably the same type of your inputs.
Objects or arrays in the inputs are not mutated. This is very important.
Testing
$ npm test
For unit tests we use AVA, Istanbul CLI and JS Standard notation.
I aim to have 100% code coverage, which it is at the moment.
Contributing
All contributions are welcome. Please stick to Standard JavaScript notation and supplement the test.js
with new unit tests covering your feature(s).
If you see anything incorrect whatsoever, do raise an issue. If you file a pull request, I'll do my best to help you to get it merged promptly. If you have any comments on the code, including ideas how to improve things, don't hesitate to contact me by email.
Difference from object-assign
object-assign
doesn't compare types of what's merged.
object-assign will simply take first argument object, overwrite the second one onto it. Then it will take the result and overwrite third argument object onto it. And so on. Every subsequent object's key will overwrite an existing one.
It's best to use object-assign
when you care little about a base object (first input argument), for example when it's a default values' object. In such cases, when the base object (first argument of object-assign
) is overwritten, that's OK.
However, when all incoming (second arg onwards) objects can contain placeholder values in a Boolean format, object-assign
doesn't work, because any Boolean placeholder key values will overwrite base object's real values in String.
When you want to merge objects seeking maximum data retention, the 'object-merge-advanced' is the best.
For example, in my email template builds, I import SCSS variables file as an object. I also import variables for each template, and template variables object overwrites anything existing in SCSS variables object.
That's because I want to be able to overwrite global colours per-template when needed.
Now imagine, we're merging those two objects, and SCSS variables object has a key "mainbgcolor": "#ffffff"
. Now, a vast majority of templates don't need any customisation for the main background, therefore in their content JSON files the key is set to default, Boolean false
: "mainbgcolor": false
.
If merging were done using object-assign
, placeholder false
would overwrite real string value "#ffffff
. That means, HTML would receive "false" as a CSS value, which is harsh pink!
If merging were done using this library, object-merge-advanced
, all would be fine, because String trumps Boolean — placeholder false
s would not overwrite default SCSS string values.
Licence
MIT License (MIT)
Copyright (c) 2017 Codsen Ltd, Roy Reveltas
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.