Merge objects in array of object removing duplicates - javascript

I have this array of objects:
const a = [
{
id: 1,
name: 'John',
role: 'admin'
},
{
id: 1,
name: 'John',
role: 'user'
},
{
id: 2,
name: 'Max',
role: 'user'
}
]
I would like to have a result like this, so having one object for id:1 and a merged array in role property:
const a = [
{
id: 1,
name: 'John',
role: ['admin', 'user']
},
{
id: 2,
name: 'Max',
role: 'user'
}
]
EDIT:
I am able to remove duplicates when I have just to properties in the object. In my case I don't know how to retrieve the name property using the following snippet:
const b = [...new Set(a.map(d => d.id))].map(obj => {
return {
id: obj,
data: a.filter(d => d.id === obj).map(d => d.role)
}
})

You could take an object for grouping and use an array for additional roles.
const
data = [{ id: 1, name: 'John', role: 'admin' }, { id: 1, name: 'John', role: 'user' }, { id: 2, name: 'Max', role: 'user' }],
result = Object.values(data.reduce((r, o) => {
if (!r[o.id]) r[o.id] = { ...o };
else r[o.id].role = [].concat(r[o.id].role, o.role);
return r;
}, {}));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

It can be done very simply with a reducer:
const a = [
{
id: 1,
name: 'John',
role: 'admin'
},
{
id: 1,
name: 'John',
role: 'user'
},
{
id: 2,
name: 'Max',
role: 'user'
}
]
const b = a.reduce((acc, el)=>{
const existingEl = acc.find(accEl=>accEl.id === el.id)
if(existingEl) existingEl.role.push(el.role)
// a very inelegant way of building a shallow copy with
// a bit of a data structure change
else acc.push({id: el.id, name: el.name, role:[el.role]})
return acc
}, [])
console.log(b)

give this a try
const a = [
{
id: 1,
name: 'John',
role: 'admin'
},
{
id: 1,
name: 'John',
role: 'user'
},
{
id: 2,
name: 'Max',
role: 'user'
}
]
const newArr = a.reduce((acc, val) => {
const findIndex = acc.findIndex(f => f.id === val.id);
if (findIndex > -1) {
if ((typeof acc[findIndex].role === 'string')) {
acc[findIndex].role = [acc[findIndex].role, val.role]
} else {
acc[findIndex].role.push(val.role)
}
} else {
acc.push(val)
}
return acc
}, []);
console.log(newArr)

You can iterate over each item in your input, storing its data on an object keyed by the item's id property. Using a Set to collect the roles during iteration ensures that no duplicates will exist in the end result:
function mergeRoles (users) {
const merged = {};
for (const {id, name, role} of users) {
(merged[id] ??= {id, name, role: new Set([role])}).role.add(role);
}
return Object.values(merged).map(user => ({...user, role: [...user.role]}));
}
const input = [
{ id: 1, name: 'John', role: 'admin' },
{ id: 1, name: 'John', role: 'user' },
{ id: 2, name: 'Max', role: 'user' },
];
const result = mergeRoles(input);
console.log(result);

For problems like this I usually turn the array into an object dictionary to merge all the duplicates, then convert the dictionary back to an array:
const a = [{
id: 1,
name: 'John',
role: 'admin'
},
{
id: 1,
name: 'John',
role: 'user'
},
{
id: 2,
name: 'Max',
role: 'user'
}
];
// Merge duplicates using object dictionary.
let itemsById = {};
for (let item of a) {
if (!itemsById[item.id]) {
// Id not seen yet.
item.role = [item.role];
itemsById[item.id] = item;
} else {
// Duplicate Id.
itemsById[item.id].role.push(item.role);
}
}
// Convert object dictionary back to array.
let newArray = [];
for (const id in itemsById) {
let item = itemsById[id];
if (item.role.length == 1) {
item.role = item.role[0];
}
newArray.push(item);
}
console.log(newArray);

Related

How to add value in array which is present inside objects array?

here, I want to update data in such way that if same person with same company comes then it's value of client should come under one array.
input data :
const employee = [
{
name: 'John',
company: 'abc',
client: 'BMW'
},
{
name: 'Jack',
company: 'abc',
client: 'Volvo'
},
{
name: 'John',
company: 'abc',
client: 'Mercedes'
}
]
Expected output:
[
{
name: 'John',
company: 'abc',
client: ['BMW', 'Mercedes']
},
{
name: 'Jack',
company: 'abc',
client: 'Volvo'
}
]
Here is a way to do it in O(n) time. If you're fine with O(n^2) time, I'd suggest using reduce (as Mina's answer shows) since it's more straightforward.
const inputs = [{
name: 'John',
company: 'abc',
client: 'BMW'
}, {
name: 'Jack',
company: 'abc',
client: 'Volvo'
}, {
name: 'John',
company: 'abc',
client: 'Mercedes'
}];
const itemsByNameCompany = {};
for (let input of inputs) {
const nameCompany = `${input.name},${input.company}`;
itemsByNameCompany[nameCompany] = itemsByNameCompany[nameCompany] || [];
itemsByNameCompany[nameCompany].push(input);
}
const outputs = [];
for (let key in itemsByNameCompany) {
const items = itemsByNameCompany[key];
const clients = items.map(item => item.client);
const item = {
...items[0],
client: clients.length === 1 ? clients[0] : clients
};
outputs.push(item);
}
console.log(outputs);
There are lots of way we can do that, Reduce, map, forOf or you can use that as well:
let employee = [
{
name: 'John',
company: 'abc',
client: 'BMW'
},
{
name: 'Jack',
company: 'abc',
client: 'Volvo'
},
{
name: 'John',
company: 'abc',
client: 'Mercedes'
}
];
let employees = {};
employee.forEach(employee => {
if(employees[employee.name]){
employees[employee.name].client = [...employees[employee.name].client, employee.client];
} else {
employees[employee.name] = { ...employee, client: [employee.client] };
}
});
console.log(employees); // Simple Object group by name
console.log(Object.values(employees)); // Convert to Array
Hope it will help you to solve.
You will need a function that does this:
Check if the same name and company combination exists
If it exists, then update the client property
If the combination doesn't exist, add it to the same array
const result = [];
function _addData(obj){
const index = result.findIndex((item) =>
item.name === obj.name && item.company === obj.company);
if(index!=-1){
const client = result[index].client;
if(Array.isArray(client)){
client.push(obj.client);
} else{
result[index].client = [client, obj.client];
}
} else{
result.push(obj);
}
}
You need to add a function that checks if the client already is an array. If it is then you can just push the new client. If its a string then you can take the previous value and the new value and write both of them into a new array.
You can do it like this:
const employee = [
{
name: 'John',
company: 'abc',
client: 'BMW'
},
{
name: 'Jack',
company: 'abc',
client: 'Volvo'
},
{
name: 'John',
company: 'abc',
client: 'Mercedes'
}
]
function addClient(index, client) {
if (Array.isArray(employee[index].client)) {
employee[index].client.push(client);
} else {
employee[index].client = [employee[index].client, client];
}
}
addClient(0, 'New Client')
addClient(0, 'New Client2')
addClient(1, 'New Client3')
console.log(employee)
You can use reduce method to group the employees together it they have equal name and company.
const employees = [
{
name: 'John',
company: 'abc',
client: 'BMW'
},
{
name: 'Jack',
company: 'abc',
client: 'Volvo'
},
{
name: 'John',
company: 'abc',
client: 'Mercedes'
}
]
const result = employees.reduce((acc, item) => {
const itemInAcc = acc.find(i => i.name === item.name && i.company === item.company);
if (itemInAcc) {
let client = itemInAcc.client
itemInAcc.client = [...(Array.isArray(client) ? client : [client]), item.client];
} else {
acc.push(item)
}
return acc;
}, [])
console.log(result)
Hi you can use reduce to group
employee.reduce((grouped, next) => {
if (grouped.some(gItem => gItem.name === next.name && gItem.company === next.company)) {
grouped = grouped.map(gItem => {
if (gItem.name === next.name && gItem.company === next.company) {
gItem.client = Array.isArray(gItem.client) ? [...gItem.client, next.client] : [gItem.client, next.client]
}
return gItem
})
} else {
grouped.push(next)
}
return grouped
}, [])
welcome to stackoverflow Akshay Kamble
in you case you need to create new JSON object to collect filtered data,
this is my simple code just using forEach
const employee = [{
name: 'John',
company: 'abc',
client: 'BMW'
},
{
name: 'Jack',
company: 'abc',
client: 'Volvo'
},
{
name: 'John',
company: 'abc',
client: 'Mercedes'
}
]
const newEmployee = [];
employee.forEach(e => {
let obj = newEmployee.find(o => o.name === e.name);
if (obj) {
if (obj.client instanceof Array) {
obj.client.push(e.client)
} else {
obj.client = [e.client]
}
} else {
e.client = [e.client];
newEmployee.push(e);
}
})
console.log(newEmployee)

Convert an array of object to a new array of object with keys in javscript

I have an array of objects in the format below and would like to transform it into a new array of objects using a property as a key. The key should be unique. See shape of the object below
const mockedList = [
{
email: 'aaa#example.com',
id: '5052',
name: 'Java',
},
{
email: 'bbb#example.com',
id: '5053',
name: 'Python',
},
{
email: 'aaa#example.com',
id: '5054',
name: 'C#',
},
{
email: 'bbb#example.com',
id: '5055',
name: 'Javascript',
},
];
I would like to transform this and get an array of objects with keys and values in this format.
[
{
email: 'bbb#example.com',
languages: [
{
email: 'bbb#example.com',
id: '5055',
name: 'Javascript',
},
{
email: 'bbb#example.com',
id: '5053',
name: 'Python',
},
]
},
{
email: 'aaa#example.com',
languages: [
{
email: 'aaa#example.com',
id: '5052',
name: 'Java',
},
{
email: 'aaa#example.com',
id: '5054',
name: 'C#',
},
]
}
]
I've tried using map-reduce
const result = mockedList.reduce((r, a) => {
r[a.email] = r[a.email] || [];
r[a.email].push(a);
return r;
}, Object.create(null));
But did not get the right shape of data
You can do:
const mockedList = [{email: 'aaa#example.com',id: '5052',name: 'Java',},{email: 'bbb#example.com',id: '5053',name: 'Python',},{email: 'aaa#example.com',id: '5054',name: 'C#',},{ email: 'bbb#example.com', id: '5055', name: 'Javascript' },]
const mockedListHash = mockedList.reduce((a, c) => {
a[c.email] = a[c.email] || { email: c.email, languages: [] }
a[c.email].languages.push(c)
return a
}, {})
const result = Object.values(mockedListHash)
console.log(result)
In case you want to clean the repeated emails within languages:
const mockedList = [{email: 'aaa#example.com',id: '5052',name: 'Java',},{email: 'bbb#example.com',id: '5053',name: 'Python',},{email: 'aaa#example.com',id: '5054',name: 'C#',},{ email: 'bbb#example.com', id: '5055', name: 'Javascript' },]
const mockedListHash = mockedList.reduce((a, c) => {
a[c.email] = a[c.email] || { email: c.email, languages: [] }
a[c.email].languages.push({
id: c.id,
name: c.name,
})
return a
}, {})
const result = Object.values(mockedListHash)
console.log(result)
Here is another option with simple for loop
// Array
const mockedList = [
{
email: 'aaa#example.com',
id: '5052',
name: 'Java'
},
{
email: 'bbb#example.com',
id: '5053',
name: 'Python'
},
{
email: 'aaa#example.com',
id: '5054',
name: 'C#'
},
{
email: 'bbb#example.com',
id: '5055',
name: 'Javascript'
}
];
// Set new object
const newObj = {};
// Use regular loop
for(const el of mockedList) {
// Use email as key
// If key already exist, add info
// to it's languages array
if(newObj[el.email]) newObj[el.email].languages.push(el);
else newObj[el.email] = {
email: el.email,
languages: [el]
}
}
// Test
console.log(newObj);
// If you need just array of objects,
// without email as key, then transform it
const newArr = Object.keys(newObj).map((key) => newObj[key]);
// Test
console.log(newArr);

Recursive Filter on Nested array

I need to filter a nested structure, that looks like this, based on query. I need to return all objects, including the parent object of the subtree, which match the query string in object name. Please help, i am stuck.
[
{
name: 'bob',
type: 1,
children: [
{
name: 'bob',
type: 2,
children: [
{
name: 'mike',
type: 3,
children: [
{
name:'bob',
type: 7,
children: []
},
{
name: 'mike',
type: 9,
children: []
}
]
}
]
},
{
name: 'mike',
type: 2
}
]
}
]
Right now i am able to find a match in the tree recursively, but the function returns the object on the first match and does not search deeper on sub levels in the same object. Any suggestions, how i can modify the code to go search through all levels recursively?
return tree.map(copy).filter(function filterNested(node) {
if (node.name.toLowerCase().indexOf(query) !== -1) {
return true;
}
if (node.children) {
return (node.children = node.children.map(copy).filter(filterNested))
.length;
}
});
if I am searching for query 'bob', the expected result should be,
const arr = [
{
name: 'bob',
type: 1,
children: [
{
name: 'bob',
type: 2,
children: [
{
name: 'mike',
type: 3,
children: [
{
name:'bob',
type: 7
},
]
}
]
},
]
}
]
You could reduce the array and build new objects with optional children, if they have a length not zero.
function filter(array, fn) {
return array.reduce((r, o) => {
var children = filter(o.children || [], fn);
if (fn(o) || children.length) r.push(Object.assign({}, o, children.length && { children }));
return r;
}, []);
}
var data = [{ name: 'bob', type: 1, children: [{ name: 'bob', type: 2, children: [{ name: 'mike', type: 3, children: [{ name: 'bob', type: 7 }, { name: 'same', typ: 9 }] }] }, { name: 'mike', type: 2 }] }],
result = filter(data, ({ name }) => name.toLowerCase() === 'bob');
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Something like this, perhaps?
function nestedFilter(query, nodes) {
return nodes.reduce((result, node) => {
const filteredChildren = node.children && nestedFilter(query, node.children);
const nameMatches = node.name.toLowerCase().indexOf(query) !== -1;
const childrenMatch = filteredChildren && filteredChildren.length;
const shouldKeep = nameMatches || childrenMatch;
return shouldKeep
? result.concat({ ...node, children: filteredChildren })
: result;
}, []);
}
You can use a recursive reduce for this, where you check if there are any children or if the name matches before you accumulate the object:
const example = [{
name: 'bob',
type: 1,
children: [{
name: 'bob',
type: 2,
children: [{
name: 'mike',
type: 3,
children: [{
name: 'bob',
type: 7,
children: []
},
{
name: 'mike',
type: 9,
children: []
}
]
}]
},
{
name: 'mike',
type: 2
}
]
}];
function reduceName(accum, item, matcher) {
item.children = (item.children || []).reduce((a,i)=>reduceName(a,i,matcher),[]);
if (!item.children.length) delete item.children;
if (matcher(item) || item.children) accum.push(item);
return accum;
}
console.log(example.reduce((a,i)=>reduceName(a,i,x=>x.name==='bob'),[]));
I would just use good old visitor pattern to traverse and build new tree.
class Visitor {
constructor(predicate) {
this.predicate = predicate;
}
visit(item) {
if (Array.isArray(item)) {
return this.visitArray(item);
} else if (item) {
return this.visitItem(item);
}
}
visitArray(item) {
const result = [];
for (let e of item) {
const item = this.visit(e);
if (item) {
result.push(item);
}
}
return result;
}
visitItem(item) {
const children = this.visit(item.children);
const hasChildren = (children && children.length > 0);
if (hasChildren || this.predicate(item)) {
return {
name: item.name,
type: item.type,
children: children
}
}
return null;
}
}
const visitor = new Visitor((item) => item.name === "bob");
const result = visitor.visit(data);
console.log(result);
A wonderful time to learn about mutual recursion and continuation passing style
const data =
[{name:'bob',type:1,children:[{name:'bob',type:2,children:[{name:'mike',type:3,children:[ {name:'bob',type:7,children:[]},{name:'mike',type:9,children:[]}]}]},{name:'mike',type:2}]}]
const identity = x => x
const search = (all = [], query = identity, pass = identity) =>
all.flatMap(v => search1(v, query, pass))
const search1 = (one = {}, query = identity, pass = identity) =>
query(one)
? pass([ { ...one, children: search(one.children, query) } ])
: search
( one.children
, query
, children =>
children.length === 0
? pass([])
: pass([ { ...one, children } ])
)
const result =
search(data, x => x.name === "bob")
console.log(JSON.stringify(result, null, 2))
I do think that this question will always be relevant so here is how I did it !
After a few hours ;)
var res = yourArray.filter(function f(el) {
if (el.children.length > 0) {
el.children = el.childreb.filter(f);
}
if (el.name === "bob") return true; // you can put the condition you need here.
})
//res is your returned array
Trully hope this code will benefit some of youl :)

How to merge and return new array from object in es6

Suppose there are two objects.
const a = [
{ id: '1-1-1', name: 'a111' },
{ id: '1-1-2', name: 'a112' },
{ id: '1-2-1', name: 'a121' },
{ id: '1-2-2', name: 'a122' },
{ id: '2-1-1', name: 'a211' },
{ id: '2-1-2', name: 'a212' }
]
const b = ['1-1', '1-2', '2-1']
and the result
{
'1-1':[
{ id: '1-1-1', name: 'a111' },
{ id: '1-1-2', name: 'a112' },
],
'1-2':[
{ id: '1-2-1', name: 'a121' },
{ id: '1-2-2', name: 'a122' },
],
'2-1':[
{ id: '2-1-1', name: 'a211' },
{ id: '2-1-2', name: 'a212' },
]
}
Basically, I want to group the data.
I use includes to check if the item from b to match the id from a. Then construct the new array.
This is my attempt(fiddle):
return b.map(item => a.map(jtem => {
if(jtem.id.includes(item)){
return {
[item]: jtem
}
}
}))
For somehow, it doesn't work.
and, is there a clever way to avoid the nested for loop or map function?
You can do that in following steps:
Apply reduce() on the array b
During each iteration use filter() on the the array a
Get all the items from a which starts with item of b using String.prototype.startsWith()
At last set it as property of the ac and return ac
const a = [
{ id: '1-1-1', name: 'a111' },
{ id: '1-1-2', name: 'a112' },
{ id: '1-2-1', name: 'a121' },
{ id: '1-2-2', name: 'a122' },
{ id: '2-1-1', name: 'a211' },
{ id: '2-1-2', name: 'a212' }
]
const b = ['1-1', '1-2', '2-1']
let res = b.reduce((ac,b) => {
ac[b] = a.filter(x => x.id.startsWith(b));
return ac;
},{})
console.log(res)
As suggested by #Falco is the comments that It would be better to scan over the a once as its large. So here is that version.Actually its better regarding performance
const a = [
{ id: '1-1-1', name: 'a111' },
{ id: '1-1-2', name: 'a112' },
{ id: '1-2-1', name: 'a121' },
{ id: '1-2-2', name: 'a122' },
{ id: '2-1-1', name: 'a211' },
{ id: '2-1-2', name: 'a212' }
]
const b = ['1-1', '1-2', '2-1']
let res = a.reduce((ac,x) => {
let temp = b.find(y => x.id.startsWith(y))
if(!ac[temp]) ac[temp] = [];
ac[temp].push(x);
return ac;
},{})
console.log(res)
Note: startsWith is not supported by I.E. So you can create polyfill using indexOf
if(!String.prototype.startWith){
String.prototype.startsWith = function(str){
return this.indexOf(str) === 0
}
}

How to create a new array of objects form object without duplicates ? (ES6)

I would like to create an array of all "department" from the "users" array without duplicate in ES6.
I've tried with forEach, reduce, filter, without success...
Users array:
let users = [{
firstname: 'test',
department: {
id: 1,
name: 'hello'
}
},
{
firstname: 'test2',
department: {
id: 2,
name: 'hello2'
}
},
{
firstname: 'test2',
department: {
id: 1,
name: 'hello'
}
}
]
Result expected:
// Expected
departments = [{
id: 1,
name: 'hello'
},
{
id: 2,
name: 'hello2'
}
] */
My own experiment:
let departments = []
users.forEach(user => {
console.log('-------------------')
console.log(departments)
console.log(user)
console.log(user.department)
console.log(departments.includes(user.department))
if (!departments.includes(user.department)) {
departments.push(user.department)
}
console.log(departments)
})
console.log(departments)
Thanks for your help!
Problem:
Your problem is that you are checking for departments with Array#includes() which is rather used with primitives such as Number and string and doesn't compare objects, try not to use it as it's not compatible with IE also.
Solution:
You can do it using Array#map() and Array#filter() methods:
var deps = users.map(u => u.department);
let results = deps.filter((item, pos) => {
return deps.map(v => v.id).indexOf(item.id) == pos;
});
First map the items to keep only the department object.
Then filter the departments to exclude the ones that has the same id.
Demo:
This is a working demo:
let users = [{
firstname: 'test',
department: {
id: 1,
name: 'hello'
}
},
{
firstname: 'test2',
department: {
id: 2,
name: 'hello2'
}
},
{
firstname: 'test2',
department: {
id: 1,
name: 'hello'
}
}
];
var deps = users.map(u => u.department);
let results = deps.filter((item, pos) => {
return deps.map(v => v.id).indexOf(item.id) == pos;
});
console.log(results);
Just map to the departments, then filter out based on the id:
const ids = new Set;
const result = users
.map(user => user.department)
.filter(({ id }) => !ids.has(id) && ids.add(id));
(This is O(n) as Set lookup / insertion is O(1))
You can use Array.reduce() for that:
let users = [{
firstname: 'test',
department: {
id: 1,
name: 'hello'
}
},
{
firstname: 'test2',
department: {
id: 2,
name: 'hello2'
}
},
{
firstname: 'test2',
department: {
id: 1,
name: 'hello'
}
}
];
let departments = users.reduce((acc, obj)=>{
let exist = acc.find(({id}) => id === obj.department.id);
if(!exist){
acc.push({id:obj.department.id, name: obj.department.name});
}
return acc;
}, []);
console.log(departments);

Categories