Gathering detailed insights and metrics for @cryptexlabs/merge-anything
Gathering detailed insights and metrics for @cryptexlabs/merge-anything
Gathering detailed insights and metrics for @cryptexlabs/merge-anything
Gathering detailed insights and metrics for @cryptexlabs/merge-anything
Merge objects & other types recursively. A simple & small integration.
npm install @cryptexlabs/merge-anything
Module System
Min. Node Version
Typescript Support
Node Version
NPM Version
148 Commits
1 Branches
1 Contributors
Updated on 30 Oct 2024
TypeScript (99.15%)
JavaScript (0.85%)
Cumulative downloads
Total Downloads
Last day
-60.9%
9
Compared to previous day
Last week
-7.3%
76
Compared to previous week
Last month
1,772.3%
1,217
Compared to previous month
Last year
0%
1,282
Compared to previous year
1
4
npm i merge-anything
Merge objects & other types recursively. Fully TypeScript supported! A simple & small integration.
I created this package because I tried a lot of similar packages that do merging/deepmerging/recursive object assign etc. But all had its quirks, and all of them break things they are not supposed to break... 😞
I was looking for:
Object.assign()
but deepThis last one is crucial! In JavaScript almost everything is an object, sure, but I don't want a merge function trying to merge eg. two new Date()
instances! So many libraries use custom classes that create objects with special prototypes, and such objects all break when trying to merge them. So we gotta be careful!
merge-anything will merge objects and nested properties, but only as long as they're "plain objects". As soon as a sub-prop is not a "plain object" and has a special prototype, it will copy that instance over "as is". ♻️
1import { merge } from 'merge-anything' 2 3const starter = { name: 'Squirtle', types: { water: true } } 4const newValues = { name: 'Wartortle', types: { fighting: true }, level: 16 } 5 6const evolution = merge(starter, newValues, { is: 'cool' }) 7// returns { 8// name: 'Wartortle', 9// types: { water: true, fighting: true }, 10// level: 16, 11// is: 'cool' 12// }
In the example above, if you are using TypeScript, and you hover over evolution
, you can actually see the type of your new object right then and there. This is very powerful, because you can merge things, and without needing any
, TypeScript will know exactly how your newly merged objects look!
The return type of the merge()
function is usable as a TypeScript utility as well:
1import type { Merge } from 'merge-anything' 2 3type A1 = { name: string } 4type A2 = { types: { water: boolean } } 5type A3 = { types: { fighting: boolean } } 6 7type Result = Merge<A1, [A2, A3]>
This package will recursively go through plain objects and merge the values onto a new object.
Please note that this package recognises special JavaScript objects like class instances. In such cases it will not recursively merge them like objects, but assign the class onto the new object "as is"!
1// all passed objects do not get modified 2const a = { a: 'a' } 3const b = { b: 'b' } 4const c = { c: 'c' } 5const result = merge(a, b, c) 6// a === {a: 'a'} 7// b === {b: 'b'} 8// c === {c: 'c'} 9// result === {a: 'a', b: 'b', c: 'c'} 10// However, be careful with JavaScript object references with nested props. See below: A note on JavaScript object references 11 12// arrays get overwritten 13// (for "concat" logic, see Extensions below) 14merge({ array: ['a'] }, { array: ['b'] }) // returns {array: ['b']} 15 16// empty objects merge into objects 17merge({ obj: { prop: 'a' } }, { obj: {} }) // returns {obj: {prop: 'a'}} 18 19// but non-objects overwrite objects 20merge({ obj: { prop: 'a' } }, { obj: null }) // returns {obj: null} 21 22// and empty objects overwrite non-objects 23merge({ prop: 'a' }, { prop: {} }) // returns {prop: {}}
merge-anything properly keeps special objects intact like dates, regex, functions, class instances etc.
However, it's very important you understand how to work around JavaScript object references. Please be sure to read #a note on JavaScript object references down below.
The default behaviour is that arrays are overwritten. You can import mergeAndConcat
if you need to concatenate arrays. But don't worry if you don't need this, this library is tree-shakable and won't import code you don't use!
1import { mergeAndConcat } from 'merge-anything' 2 3mergeAndConcat( 4 { nested: { prop: { array: ['a'] } } }, 5 { nested: { prop: { array: ['b'] } } } 6) 7// returns { nested: { prop: { array: ['a', 'b'] } } },
There might be times you need to tweak the logic when two things are merged. You can provide your own custom function that's triggered every time a value is overwritten.
For this case we use mergeAndCompare
. Here is an example with a compare function that concatenates strings:
1import { mergeAndCompare } from 'merge-anything' 2 3function concatStrings(originVal, newVal, key) { 4 if (typeof originVal === 'string' && typeof newVal === 'string') { 5 // concat logic 6 return `${originVal}${newVal}` 7 } 8 // always return newVal as fallback!! 9 return newVal 10} 11 12mergeAndCompare(concatStrings, { name: 'John' }, { name: 'Simth' }) 13// returns { name: 'JohnSmith' }
Note for TypeScript users. The type returned by this function might not be correct. In that case you have to cast the result to your own provided interface
Be careful for JavaScript object reference. Any property that's nested will be reactive and linked between the original and the merged objects! Down below we'll show how to prevent this.
1const original = { airport: { status: 'dep. 🛫' } } 2const extraInfo = { airport: { location: 'Brussels' } } 3const merged = merge(original, extraInfo) 4 5// we change the status from departuring 🛫 to landing 🛬 6merged.airport.status = 'lan. 🛬' 7 8// the `merged` value will be modified 9// merged.airport.status === 'lan. 🛬' 10 11// However `original` value will also be modified!! 12// original.airport.status === 'lan. 🛬'
The key rule to remember is:
Any property that's nested more than 1 level without an overlapping parent property will be reactive and linked in both the merge result and the source
However, there is a really easy solution. We can just copy the merge result to get rid of any reactivity. For this we can use the copy-anything library. This library also makes sure that special class instances do not break, so you can use it without fear of breaking stuff!
See below how we integrate 'copy-anything':
1import { copy } from 'copy-anything' 2 3const original = { airport: { status: 'dep. 🛫' } } 4const extraInfo = { airport: { location: 'Brussels' } } 5const merged = copy(merge(original, extraInfo)) 6 7// we change the status from departuring 🛫 to landing 🛬 8merged.airport.status = 'lan. 🛬'(merged.airport.status === 'lan. 🛬')( 9 // true 10 // `original` won't be modified! 11 original.airport.status === 'dep. 🛫' 12) // true
You can then play around where you want to place the copy()
function.
Copy Anything is also fully TypeScript supported!
It is literally just going through an object recursively and assigning the values to a new object like below. However, it's wrapped to allow extra params etc. The code below is the basic integration, that will make you understand the basics how it works.
1import { isPlainObject } from 'is-what' 2 3function mergeRecursively(origin, newComer) { 4 if (!isPlainObject(newComer)) return newComer 5 // define newObject to merge all values upon 6 const newObject = isPlainObject(origin) 7 ? Object.keys(origin).reduce((carry, key) => { 8 const targetVal = origin[key] 9 if (!Object.keys(newComer).includes(key)) carry[key] = targetVal 10 return carry 11 }, {}) 12 : {} 13 return Object.keys(newComer).reduce((carry, key) => { 14 const newVal = newComer[key] 15 const targetVal = origin[key] 16 // early return when targetVal === undefined 17 if (targetVal === undefined) { 18 carry[key] = newVal 19 return carry 20 } 21 // When newVal is an object do the merge recursively 22 if (isPlainObject(newVal)) { 23 carry[key] = mergeRecursively(targetVal, newVal) 24 return carry 25 } 26 // all the rest 27 carry[key] = newVal 28 return carry 29 }, newObject) 30}
* Of course, there are small differences with the actual source code to cope with rare cases & extra features. The actual source code is here.
No vulnerabilities found.
No security vulnerabilities found.