17
u/very-kind-stranger Feb 16 '21
ES2020 introduced the nullish coalescing operator denoted by the double question marks ( ?? ). It returns the right operand ( rightExpression ) if the left operand ( leftExpression ) is null or undefined . A nullish value is a value that is either null or undefined .
1
u/rbrtbrnschn Feb 17 '21
wouldn't `||` been eligible to use in this scenario too?
given as far as my knowledge goes the or operator goes by boolean, meaning
if it's either false or falsy, which it'd be if it's null or undefined, then it'd execute the expression on the right-hand side too.or am I forgetting something?
1
u/god--of--light Feb 17 '21
Or is fine, but theres a small problem since it has the same behaviour for undefined as it does for 0 or ''
1
25
u/senocular Feb 16 '21
This merge will have some problems. You're better off sticking to a more battle-tested merge such as lodash's merge.
4
u/Crypticsafe5 Feb 17 '21
My only issue with this snippet is that it allows mutability of the schema that's passed in potentially causing data loss or data integrity issues. I'd definitely add in a check something along the lines of:
if (
typeof a[key] !== typeof b[key]
|| Array.isArray(a[key]) !== Array.isArray(b[key])
) throw Error('Non-matching schema');1
u/Pertinate Feb 17 '21
const deepMerge = (objs, origin = {}) => {
const obj = Array.isArray(objs) ? objs.pop() : objs;
if (objs.length > 0) {
deepMerge(objs, origin);
}
for (const [key, value] of Object.entries(obj)) {
if (origin[key]) {
if (typeof origin[key] === 'object') {
if (typeof value !== 'object') {
origin[key] = {
...origin[key],
value
};
} else {
origin[key] = deepMerge([origin[key], value]);
}
} else {
if (typeof value === 'object') {
origin[key] = {
value: origin[key],
...value
};
} else {
if (origin[key].hasOwnProperty('value')) {
origin[key].value = value;
} else {
origin[key] = value;
}
}
}
} else {
if (typeof value !== 'object') {
origin[key] = value;
} else {
origin[key] = deepMerge(value, origin[key]);
}
}
};
return origin;
};
console.log(JSON.stringify(deepMerge([
{
test1: {
hello: 'hi'
},
test2: {
hi: 'hello',
another: 'value'
}
},
{
test2: 1234,
test3: {
hi: 'hello'
}
},
{
test2: {
hi: 'new val'
},
test3: {
hello: 'also new val'
}
},
{
test2: 5678
}
]), null, '\t'));Here's what I came up with for combining deep objects. Not sure if it's best or not. Let me know where I can improve.
Output:
{
"test1": {
"hello": "hi"
},
"test2": {
"hi": "new val",
"another": "value",
"value": 5678
},
"test3": {
"hi": "hello",
"hello": "also new val"
}
}Mine, assuming you want to keep nested values and assigned top level values, merges all nested objects and if it is overridden with a value (like test2), it is dropped down as the 'value' key.
1
u/beforesemicolon Feb 17 '21
This is nice. I think I would expect test2 to be 5678 but I like this for supporting even more than 2 objects
2
1
Feb 17 '21
Nice, my only criticism is if you are at all concerned with prototypes. Hopefully we're in the land of functional patterns by now, but lots of projects still use OOP.
Just add one part to that merge function...
const protoA = Object.getPrototypeOf(a);
const protoB = Object.getPrototypeOf(b);
const proto = { ...protoA, ...protoB };
const obj = Object.create(proto, a);...or something like that. Forgive me, it's late and I'm not testing this atm. If someone else wants to expand on this feel free, I'm sure it would only be helpful!
1
u/backtickbot Feb 17 '21
1
u/--Explosion-- Feb 17 '21
Nice but you can do it in one line:
let object_1 = {hello: "World"};
let object_2 = {another: "Thing"};
//Object 1 props overwrite that of object 2.
let merged = {...object_1, ...object_2};1
u/backtickbot Feb 17 '21
0
Feb 16 '21
[deleted]
18
u/delventhalz Feb 16 '21
Not exactly. Object.assign or the spread operator like you demonstrated does a shallow merge. This function is a deep merge like lodash's merge.
3
Feb 17 '21
What’s the diff between shallow and deep merging?
11
u/delventhalz Feb 17 '21
Good question!
A "shallow" merge means you are just merging the top level properties. A "deep" merge, means you are recursively drilling down through all of the sub-objects and performing merges on them as well.
Probably needs an example to click. Let's say we have two incomplete objects we want to merge.
const goku = { name: 'Goku', power: 9000, move: { name: 'Kamehameha' } }; const vegeta = { name: 'Vegeta', prince: true, move: { name: 'Galick Gun', color: 'purple' } };Let's do a shallow merge.
const vegito = { ...vegeta, ...goku }; console.log(vegito); // { // name: 'Goku', // power: 9000, // prince: true, // move: { // name: 'Kamehameha' // } // }So, we started with all
vegetaproperties and then overwrote them withgokuproperties when they existed. This includes themoveproperty. We just took the whole object.Now let's do a deep merge. I'll use lodash's merge instead of writing my own.
import { merge } from 'lodash'; const gogeta = merge({}, vegeta, goku); console.log(gogeta); // { // name: 'Goku', // power: 9000, // prince: true, // move: { // name: 'Kamehameha', // color: 'purple', // } // }Mostly the same, but notice that the merge continued down into the
moveobject. Since Vegeta's move had a color, but Goku's did not, we kept the color.One other difference worth noting: in a shallow merge nested objects are not cloned. That means
gokuandvegitoshare amoveobject. If we mutate one, we will change the other.console.log(vegito.move); // { // name: 'Kamehameha' // } goku.move.awesome = true; console.log(vegito.move); // { // name: 'Kamehameha', // awesome: true // }Whoops!
4
0
Feb 16 '21
[deleted]
7
u/delventhalz Feb 16 '21 edited Feb 16 '21
If you are working with only flat objects, then a shallow merge with spread will work fine, yes. If what you want is to recursively merge any nested objects (which is what OP is doing), then spread will not work for you. It will end up just replacing nested objects entirely.
EDIT: a word
2
u/stormfield Feb 17 '21
While this is a neat trick and stuff, at the point this is being done I’d have some real questions about the business purpose of why something like this is needed.
3
u/delventhalz Feb 17 '21
Creating a default object? Combining schemas? Shallow merges are probably more common, but a deep merge is an established pattern with plenty of real world use cases.
Perhaps more importantly, OP specifically demonstrated a deep merge. Responding to him with "but there is a much simpler way to do a shallow merge!" is silly. Completely different pattern.
3
u/Dynam2012 Feb 16 '21
As long as you're OK with nested objects being shared in the resulting object.
2
u/192_168_1_x Feb 17 '21
What’s the ?? operator?