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
- Purpose
- In practice
- API
- Difference from Lodash
_.merge
- Difference from
Object.assign()
- Contributing
- Contributors
- Licence
Install
npm i object-merge-advanced
// consume via CommonJS require():
const mergeAdvanced = require('object-merge-advanced')
// or import as an ES module:
import mergeAdvanced from 'object-merge-advanced'
Here's what you'll get:
Type | Key in package.json |
Path | Size |
---|---|---|---|
Main export - CommonJS version, transpiled to ES5, contains require and module.exports |
main |
dist/object-merge-advanced.cjs.js |
12 KB |
ES module build that Webpack/Rollup understands. Untranspiled ES6 code with import /export . |
module |
dist/object-merge-advanced.esm.js |
11 KB |
UMD build for browsers, transpiled, minified, containing iife 's and has all dependencies baked-in |
browser |
dist/object-merge-advanced.umd.js |
35 KB |
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).
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 than with an empty array or boolean.
The fun does not stop here. Sometimes life demands unidirectional merges from either source or destination ("overwrite no matter what, from either side"). This can be done per object-key-basis, see opts.ignoreKeys
where first input object's key overrides the second's and opts.hardMergeKeys
for the opposite.
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 gets assigned to the merge result — the first object's (marked 1
, pink fields) or second one's (marked 2
, sky blue fields).
In some cases, we perform a custom actions:
- passing value objects back into the main function recursively (when both values are plain objects),
- when merging arrays, we pay extra attention to the options object (if present) and the contents of both arrays (taking special measures for objects within),
- Logical "OR" 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.
I use unidirectional merging when dealing with content mapping JSON files which are by definition unidirectional-flow (always overwrite normal data JSON files).
API
mergeAdvanced(input1, input2 [, { options }])
API - Input
Input argument | Type | Obligatory? | Description |
---|---|---|---|
input1 |
Anything | yes | Normally an object literal, but array or string or whatever else will work too. Can be deeply nested. |
input2 |
Anything | yes | Second thing to merge with first-one, normally an object, but can be an 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 dedicated chapter below. |
ignoreKeys |
String / Array of strings | n/a | These keys, if present on input1 , will be kept and not merged, that is, changed. You can use wildcards. |
hardMergeKeys |
String / Array of strings | n/a | These keys, if present on input2 , will overwrite their counterparts on input1 (if present) no matter what. You can use wildcards. |
mergeArraysContainingStringsToBeEmpty |
Boolean | false |
If any arrays contain strings, resulting merged array will be empty IF this setting is set to true . |
oneToManyArrayObjectMerge |
Boolean | false |
If one array has one object, but another array has many objects, when oneToManyArrayObjectMerge is true , each object from "many-objects" array will be merged with that one object from "one-object" array. Handy when setting defaults on JSON data structures. |
hardMergeEverything |
Boolean | false |
If there's a clash of anywhere, second argument's value will always overwrite first one's. That's a unidirectional merge. |
ignoreEverything |
Boolean | false |
If there's a clash of anywhere, first argument's value will always overwrite the second one's. That's a unidirectional merge. |
concatInsteadOfMerging |
Boolean | true |
If it's true (default), when object keys clash and their values are arrays, when merging, concatenate those arrays. If it's false , array contents from the first argument object's key will go intact into final result, but second array's contents will be added into result only if they don't exist in the first array. |
dedupeStringsInArrayValues |
Boolean | false |
When we merge two values and they are arrays, full of strings and only strings, this option allows to dedupe the resulting array of strings. Setting should be used in conjunction with concatInsteadOfMerging to really ensure than resulting string array contains only unique strings. |
mergeBoolsUsingOrNotAnd |
Boolean | true |
When two values are Booleans, by default, result will be calculated using logical OR on them. If you switch this to false , merging will use logical AND . Former setting is handy when dealing with JSON content driving email templates, latter is handy when merging settings ("off", false overrides default "on", true ). |
useNullAsExplicitFalse |
Boolean | false |
When set to true , null vs. anything (argument order doesn't matter) will yield false . This is used in data structures as an explicit "false" to "turn off" incoming defaults for good without the need of extra values or wrapping with conditionals in templates. |
hardArrayConcat |
Boolean | false |
When set to true , an array vs. array merge will always result from a concat operation from the input1 parameter with input2 , no matter which items are contained on those arrays. |
hardArrayConcatKeys |
String / Array of strings | n/a | These keys, if present on input1 will force hardArrayConcat option on those values. You can use wildcards. |
} |
Here are all defaults in one place:
{
mergeObjectsOnlyWhenKeysetMatches: true,
ignoreKeys: undefined,
hardMergeKeys: undefined,
mergeArraysContainingStringsToBeEmpty: false,
oneToManyArrayObjectMerge: false,
hardMergeEverything: false,
ignoreEverything: false,
concatInsteadOfMerging: true,
dedupeStringsInArrayValues: false,
mergeBoolsUsingOrNotAnd: true,
useNullAsExplicitFalse: false,
hardArrayConcat: false,
hardArrayConcatKeys: undefined,
}
opts.mergeObjectsOnlyWhenKeysetMatches
use cases
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
const obj1 = {
a: [
{
a: 'a',
b: 'b',
yyyy: 'yyyy'
}
]
}
const obj2 = {
a: [
{
xxxx: 'xxxx',
b: 'b',
c: 'c'
}
]
}
const 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:
const 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.
Difference from Lodash _.merge
Lodash _.merge gets stuck when encounters a mismatching type values within plain objects. It's neither suitable for merging AST's, nor for deep recursive merging.
Difference from Object.assign()
Object.assign()
is just a hard overwrite of all existing keys, from one object to another. It does not weigh the types of the input values and will happily overwrite the string value with a boolean placeholder.
Object.assign()
is not for merging data objects, it's for setting defaults in the options objects.
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 pink!
If merging were done using object-merge-advanced
, all would be fine, because String trumps Boolean — placeholder false
s would not overwrite the default SCSS string values.
Contributing
If you want a new feature in this package or you would like us to change some of its functionality, raise an issue on this repo.
If you tried to use this library but it misbehaves, or you need an advice setting it up, and its readme doesn't make sense, just document it and raise an issue on this repo.
If you would like to add or change some features, just fork it, hack away, and file a pull request. We'll do our best to merge it quickly. Code style is
airbnb-base
, only without semicolons. If you use a good code editor, it will pick up the established ESLint setup.
Contributors
Thanks goes to these wonderful people (hover the cursor over contribution icons for a tooltip to appear):
Roy Revelt 💻 📖 ⚠️ |
Jabi 💻 📖 ⚠️ |
Jason Ware 🐛 |
---|
This project follows the all-contributors specification. Contributions of any kind are welcome!
Licence
MIT License (MIT)
Copyright © 2018 Codsen Ltd, Roy Revelt