I am struggling to merge 2 objects. Both of them have nested objects and the value of the arbitrary key can be an empty string.
const obj1 = { name: "Arthur", lastName: "King", address: {
street: "",
city: "Some city",
zip: "26772",
}};
const obj2 = { name: "", lastName: "", address: {
street: "Some street",
city: "",
zip: "",
}};
Object.assign
and spread operator are not working as they are doing shallow merge which is not my case, even if I separately merge the nested objects.
The problem is that merging 2 objects which have empty string values doesn't work as expected as Javascript treats empty strings as a value not like undefined
or null
.
const merged = {...obj1, ...obj2, address: {...obj1.address, ...obj2.address}}
The result will be
{
"name": "",
"lastName": "",
"address": {
"street": "Some street",
"city": "",
"zip": ""
}
}
The desired result could be the below one, but somehow it doesn't ignore empty values and treats them like a value.
{
"name": "",
"lastName": "",
"address": {
"street": "Some street",
"city": "Some city",
"zip": "26772"
}
}
One of the workarounds could be removing all the empty string values from the objects and then do merge but IMHO it's a total overkill.
I am struggling to merge 2 objects. Both of them have nested objects and the value of the arbitrary key can be an empty string.
const obj1 = { name: "Arthur", lastName: "King", address: {
street: "",
city: "Some city",
zip: "26772",
}};
const obj2 = { name: "", lastName: "", address: {
street: "Some street",
city: "",
zip: "",
}};
Object.assign
and spread operator are not working as they are doing shallow merge which is not my case, even if I separately merge the nested objects.
The problem is that merging 2 objects which have empty string values doesn't work as expected as Javascript treats empty strings as a value not like undefined
or null
.
const merged = {...obj1, ...obj2, address: {...obj1.address, ...obj2.address}}
The result will be
{
"name": "",
"lastName": "",
"address": {
"street": "Some street",
"city": "",
"zip": ""
}
}
The desired result could be the below one, but somehow it doesn't ignore empty values and treats them like a value.
{
"name": "",
"lastName": "",
"address": {
"street": "Some street",
"city": "Some city",
"zip": "26772"
}
}
One of the workarounds could be removing all the empty string values from the objects and then do merge but IMHO it's a total overkill.
Share Improve this question edited Apr 23, 2022 at 15:41 Norayr Ghukasyan asked Apr 23, 2022 at 15:11 Norayr GhukasyanNorayr Ghukasyan 1,4182 gold badges19 silver badges39 bronze badges 9- 2 What if both values of some property are not the empty string? – MikeM Commented Apr 23, 2022 at 15:18
- Can you explain more about the merging requirements you're trying to achieve? what would be each value in the target object depending on those in obj1 and obj2? – Majed Badawi Commented Apr 23, 2022 at 15:18
-
So, in the desired result only the
"nested object"
named"address"
must be populated (ie, merged usingobj1
&obj2
values) and other props (name
,lastName
, and any others if present) need to be set to empty-string, correct? – jsN00b Commented Apr 23, 2022 at 15:19 - @jsN00b Let's just ignore top-level values, I only need nested objects to be merged correctly. – Norayr Ghukasyan Commented Apr 23, 2022 at 15:25
-
@MikeM Then the source branch should have higher priority.And if the target branch's nested object value is filled string and the source's nested object value is empty, it should keep the filled one, like it should treat as
EMPTY STRING === undefined
– Norayr Ghukasyan Commented Apr 23, 2022 at 15:29
3 Answers
Reset to default 4You could greate a new object by taking the entries and check if a value is an object, then take the merged nested objects as value.
This approach works only for same object structures.
const
merge = (a, b) => Object.fromEntries(Object
.entries(a)
.map(([k, v]) => [k, v && typeof v === 'object'
? merge(v, b[k])
: v || b[k]
])
),
obj1 = { name: "Arthur", lastName: "King", address: { street: "", city: "Some city", zip: "26772" } },
obj2 = { name: "", lastName: "", address: { street: "Some street", city: "", zip: "" } },
merged = merge(obj1, obj2);
console.log(merged);
.as-console-wrapper { max-height: 100% !important; top: 0; }
const obj1 = { name: "Arthur", lastName: "King", address: {
street: "",
city: "Some city",
zip: "26772",
}};
const obj2 = { name: "", lastName: "", address: {
street: "Some street",
city: "",
zip: "",
}};
// ===
function mergeObject (a, b) {
const merged = {};
Object.entries(a).forEach(([key, aValue]) => {
const bValue = b[key];
let mergedValue = null;
if (typeof aValue === 'object') {
mergedValue = mergeObject(aValue, bValue);
}
if (typeof aValue !== 'object') {
const isAValueEmpty = aValue === '' || aValue === undefined || aValue === null;
mergedValue = isAValueEmpty ? bValue : aValue;
}
merged[key] = mergedValue;
});
return merged;
}
console.log(mergeObject(obj1, obj2));
The idea here is that you have a particular type of "is empty" that doesn't match the default. So, we make a custom function to use that condition and then use recursive calls to that function as-needed.
Objects don't have the easiest way to iterate on them with myObject.map()
like arrays, but since you know the structure you expect we can assume that Object.entries(myObject)
will get what you expect.
...
Alternative based on some of the discussion although not sure how readable this is.
The key point is the way the empty status is determined. The use of switch / if / else / ternary I think is up to preference.
const obj1 = { name: "Arthur", lastName: "King", address: {
street: "",
city: "Some city",
zip: "26772",
}};
const obj2 = { name: "", lastName: "", address: {
street: "Some street",
city: "",
zip: "",
}};
// ===
function mergeObject (a, b) {
function isValueEmpty(value) {
return value === ''
|| value === undefined
|| value === null;
}
return Object.fromEntries(Object.entries(a).map(([key, aValue]) => {
const bValue = b[key];
return [
key,
typeof aValue === 'object'
? mergeObject(aValue, bValue)
: (isValueEmpty(aValue)
? bValue
: aValue
)
];
}));
}
console.log(mergeObject(obj1, obj2));
You can recursively merge the items. Note that the snippet below has no support for arrays yet, because you had no arrays. If you want to support arrays as well, let me know.
function merge(main, secondary, deeper) {
let primary = deeper ? structuredClone(main) : main;
for (let sKey in secondary) {
if (!primary[sKey]) primary[sKey] = secondary[sKey];
else {
if (typeof secondary === "object") {
merge(primary[sKey], secondary[sKey], deeper);
} else {
if ([undefined, null, ""].indexOf(primary[sKey]) >= 0) primary[sKey] = secondary[sKey];
}
}
}
return primary;
}
const obj1 = { name: "Arthur", lastName: "King", address: {
street: "",
city: "Some city",
zip: "26772",
}};
const obj2 = { name: "", lastName: "", address: {
street: "Some street",
city: "",
zip: "",
}};
console.log(merge(obj1, obj2));