Merging two Javascript objects with similar keys - javascript

I need to merge two objects(obj1, obj2), that happen to share similar keys.
obj1 = {
0:{"Example1": "Example1"},
1:{"Example1": "Example1"},
2:{"Example1": "Example1"}
}
obj2 = {
0:{"Example2": "Example2"},
1:{"Example2": "Example2"},
2:{"Example2": "Example2"}
}
Expected result:
obj3 = {
0:{"Example1": "Example1"},
1:{"Example1": "Example1"},
2:{"Example1": "Example1"},
3:{"Example2": "Example2"},
4:{"Example2": "Example2"},
5:{"Example2": "Example2"},
}
Usual approach when merging two objects:
const obj3 = Object.assign({}, obj1, obj2);
Problem: They do share many keys, as such, in obj3, only the contents of obj2 are going to be found.
My approach:
let obj3= Object.assign({}, obj1);
for(let i=0; i<obj2.length; i++) {
obj3[obj3.length + i] = obj2[i];
}
Question: Is there, another more elegant, pre-defined way of merging two objects with similar keys?

Although I still think obj1 and obj2 should be arrays...
const obj1 = {
0:{"Example1": "Example1"},
1:{"Example1": "Example1"},
2:{"Example1": "Example1"}
};
const obj2 = {
0:{"Example2": "Example2"},
1:{"Example2": "Example2"},
2:{"Example2": "Example2"}
}
const result = Object.fromEntries(
Object.values(obj1) // get the values of the first object
.concat(Object.values(obj2)) // get the values of the second object and add them to the values of the first
.map((value, index) => [ index, value ]) // re-index them
// or if you need actual copies of the "inner" objects
/*
.map((value, index) => [
index,
Object.assign({}, value)
])
*/
);
console.log(result);
Is this "more elegant". Maybe...

The objects in your code are key-value pairs rather than a simple list (array).
From the looks of it there are only two possible scenarios:
Your objects have a meaningful, unique key associated to them (for example, this very question on stackoverflow has key 69316153 – look at the URL bar). In this case, you really can't merge the two as they have conflicting keys. Think if there was another question on this website with the same ID as this one!
The keys are not meaningful and you're happy with the same object being re-assigned a different key. In this case the correct data structure to use is arrays (obj1 = [{"Example": "..."}, {"Example": "..."}]).
If the latter is your situation, this code will work:
const obj3 = Object.values(obj1).concat(Object.values(obj2))
Object.values(obj) returns an array of values, discarding all of the keys.
Let's say we have:
const obj1 = {
1: { Name: "Apple" },
2: { Name: "Watermelon" },
};
const obj2 = {
2: { Name: "Pear" },
5: { Name: "Tomato" }
};
Object.values(obj1) will return [{ Name: "Apple" }, { Name: "Watermelon" }], while Object.values(obj2) will return [{ Name: "Pear" }, { Name: "Tomato" }].
With const obj3 = Object.values(obj1).concat(Object.values(obj2)) you will end up with:
obj3 = [
{ Name: "Apple" },
{ Name: "Watermelon" },
{ Name: "Pear" },
{ Name: "Tomato" }
];

Because you have key-value maps and not arrays, you can't just combine the objects. Your only way would be to iterate through all the keys and add them to the final result.
E.g. something along the lines:
const obj1 = {
0:{"Example1": "Example1"},
1:{"Example1": "Example1"},
2:{"Example1": "Example1"}
};
const obj2 = {
0:{"Example2": "Example2"},
1:{"Example2": "Example2"},
2:{"Example2": "Example2"}
};
function merge(...args) {
return Object.fromEntries( // Create entries
args // From all arguments
.flatMap(o => Object.values(o)) // Get values (discard keys)
.map((element, index) => [ index, element ]) // Remap them
);
}
console.log(merge(obj1, obj2));
// Will output
// {
// 0: { Example1: "Example1" },
// 1: { Example1: "Example1" },
// 2: { Example1: "Example1" },
// 3: { Example2: "Example2" },
// 4: { Example2: "Example2" },
// 5: { Example2: "Example2" }
// }

Related

How to add elements in an object while traversing through an array using map?

I am having an array like this :
arr = [ {id:0,name:Mark} , {id:1,name:Ron}, {id:2,name:Henry}, {id:3,name:Rose}].
I want to create an object like this :
obj1 = { Mark:false, Ron:false, Henry:false, Rose:flase }
I am using map to traverse through the array like this
let obj1 = {};
obj1 = arr.map((item)=> {
obj1[item.name] = false;
})
How can I achieve the following result?
You could map entries and build an object from the pairs.
const
data = [{ id: 0, name: 'Mark' }, { id: 1, name: 'Ron' }, { id: 2, name: 'Henry' }, { id: 3, name: 'Rose' }],
result = Object.fromEntries(data.map(({ name }) => [name, false]));
console.log(result);
Object.fromEntries() is probably the best idea. But you could also use reduce, if you got more operations on the array and want to stick to the "pipe" approach.
const arr = [
{ id: 0, name: 'Mark' },
{ id: 1, name: 'Ron' },
{ id: 2, name: 'Henry' },
{ id: 3, name: 'Rose' }
];
const objA = arr
.reduce((previous, { name }) => ({ ...previous, [name]: false }), {});
const objB = arr
.reduce((previous, { name }) => {
previous[name] = false;
return previous;
}, {});
The spreach operation {...obj} for objA does effectivly copy the object on each extension, which might not be desirable. But the modern JavaScript engines simplify those expressions anyways.
objB is the more standard approach for me. One additional benefit, in regards to Object.fromEntries() is that you can have some form of standard or default object / settings, which you can use as the start of the reduce (the second parameter) and evaluate in the collection function.
All three options are valid and depend on your code style.

Unstring an object property name javascript

I have an object similar to this:
const obj = {
id: 1,
name: {
"english-us": "John",
"english-uk": "John",
"italian-eu": "Giovanni",
},
};
I want to transfrorm every property name that is a string into a non-string one, like this:
const obj = {
id: 1,
name: {
english_us: "John",
english_uk: "John",
italian_eu: "Giovanni",
},
};
I can't modify the original object. I get it from an axios request.
You could use regex with stringify
let output = JSON.parse(JSON.stringify(obj).replace(/"(.*?)":.*?,?/g,
key=>key.replace(/\-/g, `_`)));
Output
console.log(JSON.stringify(output, null, 4));
/*
{
"id": 1,
"name": {
"english_us": "John",
"english_uk": "John",
"italian_eu": "Giovanni"
}
}*/
If you can copy the object, you could check this solution for declaring the attributes:
link
There are a few ways of achieving this. This example has a function that converts the key on every iteration of the name entries. A new names object is updated with these properties, and is later folded into a new object along with the existing properties of the original object.
const obj = {
id: 1,
name: {
"english-us": "John",
"english-uk": "John",
"italian-eu": "Giovanni",
},
};
const convert = (key) => key.replace('-', '_');
const updatedName = {};
for (const [key, value] of Object.entries(obj.name)) {
updatedName[convert(key)] = value;
}
const newObj = { ...obj, name: updatedName };
console.log(newObj);
You can convert object to JSON and convert back.
const obj = {
id: 1,
name: {
"english-us": "John",
"english-uk": "John",
"italian-eu": "Giovanni",
},
};
console.log(JSON.parse(JSON.stringify(obj)))
Two ways to clone the object and rename all keys from its name property
const obj = {
id: 1,
name: {
"english-us": "John",
"english-uk": "John",
"italian-eu": "Giovanni",
},
};
// clone obj
const myObj = window.structuredClone ?
structuredClone(obj) : JSON.parse(JSON.stringify(obj));
// rename all keys in myObj.name
Object.keys(myObj.name).forEach(key => {
myObj.name[key.replace(/\-/g, `_`)] = myObj.name[key];
delete myObj.name[key];
});
console.log(myObj.name.english_us);
// obj is untouched
console.log(obj.name[`english-us`]);
// myObj.name[`english-us`] does not exist
console.log(myObj.name[`english-us`]);
// alternative: clone and rename in one go
const myObjClone = {
...obj,
name: Object.fromEntries(
Object.entries(obj.name)
.reduce( (acc, [k, v]) =>
[ ...acc, [ k.replace(/\-/g, `_`), v ] ] , [] ) )
};
console.log(myObjClone.name.italian_eu);
// obj is untouched
console.log(obj.name[`italian-eu`]);
// myObjClone.name[`italian-eu`] does not exist
console.log(myObjClone.name[`italian-eu`]);

How to map or assign an entry to an array-item based on some of this item's conditions?

I have array of objects,
if the name is xx then push xitems to that object and
if the name is yy then push yitems to that object
Below is the code tried , and also should not use spread operator
const result = [];
var ss=arrobj.forEach(function(e){
if(e.name === 'xx'){
result.push({id: e.id, name: e.name, country:e.country, others: xitems})
}
if(e.name === 'yy'){
result.push({id: e.id, name: e.name, country:e.country, others: yitems})
}
return result;
});
var arrobj =[
{id:1, name: "xx", country: "IN"},
{id:2, name: "yy", country: "MY"},
]
xitems =[
{title: "Finance", valid: true}
]
yitems =[
{title: "Sales", valid: true}
]
Expected Output
[
{id:1, name: "xx", country: "IN",
others:[
{title: "Finance", valid: true}
]
},
{id:2, name: "yy", country: "MY",
others: [
{title: "Sales", valid: true}
]
},
]
You should use .map for this.
const arrobj = [
{ id: 1, name: "xx", country: "IN" },
{ id: 2, name: "yy", country: "MY" },
];
const xitems = [{ title: "Finance", valid: true }];
const yitems = [{ title: "Sales", valid: true }];
const result = arrobj.map((item) => {
if (item.name === "xx") {
item.others = xitems;
} else if (item.name === "yy") {
item.others = yitems;
}
return item;
});
console.log(result);
Your code works, the only issue that I identified are.
There is no need to assign var ss with arrobj.forEach. Because Array.forEach donot return a value.
No need of return result; inside Array.forEach.
Also as an improvement you can simply assign the object with key others like Object.assign({}, e, { others: xitems }), rather than returning individual key value.
Working Fiddle
const arrobj = [
{ id: 1, name: "xx", country: "IN" },
{ id: 2, name: "yy", country: "MY" },
]
const xitems = [
{ title: "Finance", valid: true }
]
const yitems = [
{ title: "Sales", valid: true }
]
const result = [];
arrobj.forEach(function (e) {
if (e.name === 'xx') {
result.push(Object.assign({}, e, { others: xitems }))
}
if (e.name === 'yy') {
result.push(Object.assign({}, e, { others: yitems }))
}
});
console.log(result)
Variables are references to an object that has a value, variables do not store values. It is pointless to try to use a variable in that manner unless you have specific parameters. If you insist on a condition then you need to identify xitems and yitems by the objects values and/or properties or by the order they came in. If you have dynamic data how would you know what xitems or yitems really is?
The example below has been made reusable as long as you meet these requirements:
Must have an array of objects as a primary parameter.
Must have at least one array of objects for each object in the primary array. If there's more the rest will be ignored.
The secondary array of objects must be in the order you want then to end up as.
The second parameter is a rest parameter (not a spread operator, although I have no idea why OP does not want to use it). This will allow us to stuff in as many object arrays as we want.
const distOther = (main, ...oAs) => {...
Next we create an array of pairs from all of the secondary arrays
let others = oAs.map(sub => ['others', sub]);
// [['others', [{...}]], [['others', [{...}]], ...]
Then we turn our attention to the primary array. We'll work our way from the inside out. .map() each object as an array of pairs by Object.entries():
main.map((obj, idx) =>
// ...
Object.entries(obj)
// ...
// [{A: 1, B: 2}, {...}] => [[['A', 1], ['B', 2]], [[...], [...]]]
Then .concat() (a spead operator would be more succinct) each array of pairs with that of the secondary array of pairs corresponding to the current index (you'll need to wrap each secondary array in another array, so the return will level off correctly):
// main.map((obj, idx) =>
// ...
// Object.entries(obj)
.concat([others[idx]])));
// [[['A', 1], ['B', 2], ['others', [{...}]], [[...], [...], ['others', [{...}]]]
Finally we'll use Object.fromEntries() to convert each array of pairs into an object.
// main.map((obj, idx) =>
Object.fromEntries(
// Object.entries(obj)
// .concat([others[idx]])));
// [{'A': 1, 'B': 2, 'others': [{...}]},...]
const objArr =[
{id:1, name: "xx", country: "IN"},
{id:2, name: "yy", country: "MY"},
];
const x =[
{title: "Finance", valid: true}
]
const y =[
{title: "Sales", valid: true}
]
const distOther = (main, ...oAs) => {
let others = oAs.map(sub => ['others', sub]);
return main.map((obj, idx) =>
Object.fromEntries(
Object.entries(obj)
.concat([others[idx]])));
};
console.log(distOther(objArr, x, y));
I would choose a map based approach as well but without the if clauses which explicitly check for expected values of the mapped item's name property.
The approach instead utilizes map's 2nd thisArg parameter which gets applied as the mapper functions this context. Such an additional object can be provided as a map/index of custom key value pairs where key equals a mapped item's name.
Thus the mapper implementation features generic code, and due to the this binding it will be provided as function statement which makes it also re-usable and, if properly named, readable / comprehensible / maintainable too.
function assignBoundNamedValueAsOthers(item) {
// the bound key value pairs.
const index = this;
// create new object and assign, according to
// `item.name`, bound named value as `others`.
return Object.assign(
{},
item,
{ others: index[item.name] ?? [] },
);
}
const arrobj = [
{ id: 1, name: "xx", country: "IN" },
{ id: 2, name: "yy", country: "MY" },
];
const xitems = [{ title: "Finance", valid: true }];
const yitems = [{ title: "Sales", valid: true }];
const result = arrobj
.map(assignBoundNamedValueAsOthers, {
// each `key` equals an expected item's `name`.
xx: xitems,
yy: yitems,
});
console.log({
result,
arrobj,
xitems,
yitems,
});
.as-console-wrapper { min-height: 100%!important; top: 0; }
As one can see, the above implementation via Object.assign creates a new object from each mapped arrobj item. Thus the original item-references remains untouched / non mutated. It does not apply for the items of xitems and yitems since both array references are directly assigned each to its newly created others property. The above log does reflect this.
In case the goal was an entirely reference free data structure one needs to slightly change the Object.assign part of assignBoundNamedValueAsOthers ...
function assignBoundNamedValueAsOthers(item) {
// the bound key value pairs.
const index = this;
// create new object and assign, according to
// `item.name`, bound named value as `others`.
return Object.assign(
{},
item, {
others: (index[item.name] ?? [])
// dereference the `others` items as well.
.map(othersItem =>
Object.assign({}, othersItem)
)
},
);
}
const arrobj = [
{ id: 1, name: "xx", country: "IN" },
{ id: 2, name: "yy", country: "MY" },
];
const xitems = [{ title: "Finance", valid: true }];
const yitems = [{ title: "Sales", valid: true }];
const result = arrobj
.map(assignBoundNamedValueAsOthers, {
// each `key` equals an expected item's `name`.
xx: xitems,
yy: yitems,
});
console.log({
result,
arrobj,
xitems,
yitems,
});
.as-console-wrapper { min-height: 100%!important; top: 0; }
In case the OP does not need to care about immutability, the entire process then changes from a map task to a forEach task, where assignBoundNamedValueAsOthers does directly change/mutate each currently processed item of arrobj, thus forEach does not return any data but always the undefined value ...
function assignBoundNamedValueAsOthers(item) {
// the bound key value pairs.
const index = this;
// mutate the original reference of the currently
// processed `item` by directly assigning, according
// to `item.name`, the bound named value as `others`.
Object.assign(
item,
{ others: index[item.name] ?? [] },
);
// no explicit return value due to
// going to be used as a `forEach` task.
}
const arrobj = [
{ id: 1, name: "xx", country: "IN" },
{ id: 2, name: "yy", country: "MY" },
];
const xitems = [{ title: "Finance", valid: true }];
const yitems = [{ title: "Sales", valid: true }];
// mutates each item of `arrobj`.
arrobj.forEach(assignBoundNamedValueAsOthers, {
// each `key` equals an expected item's `name`.
xx: xitems,
yy: yitems,
});
console.log({
arrobj,
xitems,
yitems,
});
.as-console-wrapper { min-height: 100%!important; top: 0; }

How to remove duplicates with extra keys from an array

I have two arrays:
const a1=[
{
a:{ name: "first" }
},
{
b:{ name: "second" }
},
{
c:{ name: "third" }
},
{
d:{ name: "fourth" }
}
]
const a2=[
{
a:{ name: "first", shelf: "read" }
},
{
b:{ name: "second", shelf: "current" }
}
]
I need to check if contents of a1 is in a2 and if it exists replace it in a1 (basically shelf name is missing in a1. I need to update that in a1).
My end result should look like:
[
{
a:{ name: "first", shelf: "read" }
},
{
b:{ name: "second", shelf: "current" }
}
{
c:{ name: "third" }
},
{
d:{ name: "fourth" }
}
]
I have tried something like
const x = a1.map( ele => {
a2.map( e => {
if( e.name === ele.name ) return ele
else return ({ ...ele, shelf: 'none' })
})
return ele;
})
But since I don't have access to the inner maps return value I get the original array back. One way I though of doing this was to concatenate both arrays and use reduce the array by checking the shelf name using javascript's reduce method. Can someone help me with a better method.
I would go by first creating an Auxiliary object for "a2" and when I am looping over a1, in each loop I would check the existence of the current key in the auxiliary object.
Having an Auxiliary Object save you unnecessary loops, you only traverse both the arrays only once.
const a1 = [{a: {name: "first"}}, {b: {name: "second"}}, {c: {name: "third"}}, {d: {name: "fourth"}}];
const a2 = [{a: {name: "first", shelf: "read"}}, {b: {name: "second",shelf: "current"}}];
const dict = a2.reduce((acc, ob) => {
const [k, v] = Object.entries(ob)[0];
acc[k] = v;
return acc;
}, {});
const newA1 = a1.map((ob) => {
const [k, v] = Object.entries(ob)[0];
if (dict[k]) {
Object.assign(v, dict[k]);
}
return {[k]: v};
});
console.log(newA1)
This should do the job in a concise way:
a1.map((it)=> {
const key = Object.keys(it)[0];
const a2item = a2.find( (it2) => Object.keys(it2)[0]===key );
if(a2item) {
Object.assign(it[key], a2item[key]);
}
return it;
});
Please note that the elements of the original a1 array are modified.

Pull properties from an object by their name into a different object

i'm doing a project in VueJS and i have an array of data, which consists of a number of objects.
These objects are pulled from a PHP Backend and consist of values like
id: 2123
name: "Name Value"
status: "active"
account_id: "2KGGALS2353255"
Imagine i want to split these by the keys names into a similar array but i want to have a parent object that consists of two child objects
[
0: {
core: {
id: 2123
name: "Name Value"
},
extra: {
status: "active",
account_id: "2KGGALS2353255"
}
]
The question is how can i achieve this with Javascript? I don't really want to modify the data in PHP beforehand unless this is something very unadvised to do in Javascript.
I can use VueJS and Lodash.
I was looking for lodash's pick() method.
https://lodash.com/docs/4.17.4#pick
This should work for your purpose
function separate(obj, keys) {
let target = {}, rest = {};
Object.keys(obj).forEach(function(key) {
if (keys.includes(key)) {
target[key] = obj[key];
} else {
rest[key] = obj[key];
}
});
return { target: target, rest: rest };
}
let stuff = {
id: 2123,
name: "Name Value",
status: "active",
account_id: "2KGGALS2353255"
};
let separated = separate(stuff, ['id', 'name']);
console.log({
core: separated.target,
extra: separated.rest
});
Using ES6's object destructuring, and the object rest spread proposal, which requires a babel transform, you can Array#map the array into a new array of objects in the required format:
const arr = [{"id":1,"name":"Name1","status":"active","account_id":"2KGGALS2353255"},{"id":2,"name":"Name2","status":"active","account_id":"4ABCLS2353255"},{"id":3,"name":"Name3","status":"active","account_id":"6LMNALS2353255"}];
const result = arr.map(({ id, name, ...extra }) => ({
core: {
id,
name
},
extra
}));
console.log(result);
You can do the same thing using lodash's _.pick() to the get the core, and _.omit() to get the extra:
var arr = [{"id":1,"name":"Name1","status":"active","account_id":"2KGGALS2353255"},{"id":2,"name":"Name2","status":"active","account_id":"4ABCLS2353255"},{"id":3,"name":"Name3","status":"active","account_id":"6LMNALS2353255"}];
var result = arr.map(function(obj) {
return {
core: _.pick(obj, ['id', 'name']),
extra: _.omit(obj, ['id', 'name'])
};
});
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>

Categories

Resources