Merge & Group Two Javascript array of objects and Group - javascript

I have two arrays of objects. One array contains list of items, another array contains list of categories. I want to create a new array based on categoryIds. I tried using lodash. But, couldn't get the correct solution.
I can do this using looping. But, I am looking for more clean approach.
var items = [
{
id: '001',
name: 'item1',
description: 'description of item1',
categoryId: 'cat1'
},
{
id: '002',
name: 'item2',
description: 'description of item2',
categoryId: 'cat2'
},
{
id: '003',
name: 'item3',
description: 'description of item3',
categoryId: 'cat1'
},
{
id: '004',
name: 'item4',
description: 'description of item4'
}
];
var categories = [
{
id: 'cat1',
name: 'Category1'
},
{
id: 'cat2',
name: 'Category2'
}
];
Expected output
[
{
categoryId: 'cat1',
name: 'Category1',
items: [
{
id: '001',
name: 'item1',
description: 'description of item1',
categoryId: 'cat1'
},
{
id: '003',
name: 'item3',
description: 'description of item3',
categoryId: 'cat1'
}
]
},
{
categoryId: 'cat2',
name: 'Category2',
items: [
{
id: '002',
name: 'item2',
description: 'description of item2',
categoryId: 'cat2'
}
]
},
{
categoryId: '',
name: '',
items: [
{
id: '004',
name: 'item4',
description: 'description of item4'
}
]
}
]
https://jsfiddle.net/sfpd3ppn/
Thanks for the help

The following does the trick:
var items = [{ id: '001', name: 'item1', description: 'description of item1', categoryId: 'cat1' }, { id: '002', name: 'item2', description: 'description of item2', categoryId: 'cat2' }, { id: '003', name: 'item3', description: 'description of item3', categoryId: 'cat1' }, { id: '004', name: 'item4', description: 'description of item4' } ];
var categories = [ { id: 'cat1', name: 'Category1' }, { id: 'cat2', name: 'Category2' } ];
var output = categories.concat([{id:'',name:''}]).map(function(v) {
return {
categoryId: v.id,
name: v.name,
items: items.filter(function(o) {
return o.categoryId === v.id || !o.categoryId && !v.id;
})
};
});
console.log(output);
I start by using .concat() to create a new categories array that holds the original categories items plus an "empty" category. Then I .map() that array to return category objects with your desired output structure, each of which has an items array that is produced by .filter()ing the original items array.
(Note that the items arrays within the output contain references to the same objects that were in the original items input, not copies of them. If you wanted copies you could add another .map() after the .filter().)

You can accomplish the desired result using a reduce. We are going to start with the original categories array and reduce the items array into it.
var items = [
{ id: '001', name: 'item1', description: 'description of item1', categoryId: 'cat1' },
{ id: '002', name: 'item2', description: 'description of item2', categoryId: 'cat2' },
{ id: '003', name: 'item3', description: 'description of item3', categoryId: 'cat1' },
{ id: '004', name: 'item4', description: 'description of item4' }
];
var categories = [
{ id: 'cat1', name: 'Category1' },
{ id: 'cat2', name: 'Category2' }
];
// Lets add the empty category at the beginning. This simplifies the logic.
categories.push({ id: '', name: '' });
// This is a function that will return a function to be used as a filter later on
function createFilter (category) {
return function (item) {
return item.id === category;
};
}
var mergedSet = items.reduce(function (previous, current) {
// Get the category ID of the current item, if it doesn't exist set to empty string
var categoryId = current.categoryId || '';
// Find the cateogry that matches the category ID
var category = previous.find(createFilter(categoryId));
// If the items property doesn't exists (we don't have any items), create an empty array
if (!category.items) { category.items = []; }
// Add the item the category
category.items.push(current);
// Return the current value that will be used in the next iteration.
// Note, the initial value of previous will be the intial value of categories.
return previous;
}, categories);
console.log(mergedSet);
/* Output
[
{ id: 'cat1',
name: 'Category1',
items:
[ { id: '001',
name: 'item1',
description: 'description of item1',
categoryId: 'cat1' },
{ id: '003',
name: 'item3',
description: 'description of item3',
categoryId: 'cat1' }
]
},
{ id: 'cat2',
name: 'Category2',
items:
[ { id: '002',
name: 'item2',
description: 'description of item2',
categoryId: 'cat2'
}
]
},
{ id: '',
name: '',
items:
[ { id: '004',
name: 'item4',
description: 'description of item4' } ] }
]
*/

Assuming the variables categories and items are assigned as you defined above:
const keyedCategories = _(categories)
.concat({ id: '', name: '' })
.keyBy('id')
.value();
const groupedItems = _.groupBy(items, (item) => _.get(item, 'categoryId', ''));
const result = _.reduce(groupedItems, (acc, value, key) => {
const { id: categoryId, name } = keyedCategories[key];
return _.concat(acc, { categoryId, name, items: value });
}, []);

Related

How to get list with of property from array of objects unless it contains another item with certain value?

I have an array of objects, and I need to get list with certain property from that array of objects. But i need that list to contain only those values where object was containing another property with certain element.
This is very confusing so i made an example.
Let's say i have an array with objects.
employees = [
{
n: 'case 1',
date: '2021-05-4',
id: '123',
user: [{name: 'Vlad', id: '1'}, {name: 'Misha', id: '2'}],
isPresent : true,
},
{
caseName: 'case 2',
date: '2021-05-4',
id: '124',
user: [{name: 'Alina', id: '3'}, {name: 'Alex', id: '4'}],
isPresent : true,
},
{
caseName: 'case 3',
date: '2021-05-4',
id: '126',
user: [],
isPresent : false,
},
]
And my task is to get a list of IDs from array of objects, but i need ID only from those objecrs which have isPresent as true.
So i need ['123', '124'].
I could use a loops and conditions and so on. But i wondering is it possible to do with one line ? Something like this:
employees.filter(item => { return item.isPresent === true }))
But i need only IDs not whole objects.
1) You can filter the elements with condition item.isPresent === true and then map over it to get the final result as:
employees
.filter((item) => item.isPresent === true)
.map((o) => o.id);
or you can also do as:
employees.filter((item) => item.isPresent).map((o) => o.id)
const employees = [{
n: 'case 1',
date: '2021-05-4',
id: '123',
user: [{
name: 'Vlad',
id: '1'
}, {
name: 'Misha',
id: '2'
}],
isPresent: true,
},
{
caseName: 'case 2',
date: '2021-05-4',
id: '124',
user: [{
name: 'Alina',
id: '3'
}, {
name: 'Alex',
id: '4'
}],
isPresent: true,
},
{
caseName: 'case 3',
date: '2021-05-4',
id: '126',
user: [],
isPresent: false,
},
]
const result = employees
.filter((item) => item.isPresent === true)
.map((o) => o.id);
console.log(result);
2) You can also achieve the same result using reduce as:
employees.reduce((acc, curr) => {
curr.isPresent && acc.push(curr.id);
return acc;
}, []);
const employees = [
{
n: "case 1",
date: "2021-05-4",
id: "123",
user: [
{ name: "Vlad", id: "1" },
{ name: "Misha", id: "2" },
],
isPresent: true,
},
{
caseName: "case 2",
date: "2021-05-4",
id: "124",
user: [
{ name: "Alina", id: "3" },
{ name: "Alex", id: "4" },
],
isPresent: true,
},
{
caseName: "case 3",
date: "2021-05-4",
id: "126",
user: [],
isPresent: false,
},
];
const result = employees.reduce((acc, curr) => {
curr.isPresent && acc.push(curr.id);
return acc;
}, []);
console.log(result);
You can use something like this
employees.filter((item) => item.isPresent).map((obj) => obj.id);

Merge item in array of object with the same ID on Javascript

this is how the object look:
let data = [
{
brandId: '12345',
brand: 'Adidas',
item: {
name: 'Adidas 1',
price: '200',
},
},
{
brandId: '12345',
brand: 'Adidas',
item: {
name: 'Adidas 2',
price: '230',
},
},
{
brandId: '7878',
brand: 'Nike',
item: {
name: 'Nike 1',
price: '305',
},
}
];
i want the item object will merge if the object have the same brandID :
let data = [
{
brandId: '12345',
brand: 'Adidas',
item: [
{
name: 'Adidas 1',
price: '200',
},
{
name: 'Adidas 2',
price: '230',
},
],
},
{
brandId: '7878',
brand: 'Nike',
item: {
name: 'Nike 2',
price: '316',
},
},
];
is there any javascript syntax or method to do this ? and with an explanation will be very nice, Thank You
(Assuming that your output is just a typo and name/price doesn't actually changes) You can use array reduce
let data = [
{
brandId: '12345',
brand: 'Adidas',
item: {
name: 'Adidas 1',
price: '200',
},
},
{
brandId: '12345',
brand: 'Adidas',
item: {
name: 'Adidas 2',
price: '230',
},
},
{
brandId: '7878',
brand: 'Nike',
item: {
name: 'Nike 1',
price: '305',
},
}
];
const mergedItems = data.reduce((acc, curr) => {
// check if current exist on the accumulator
const exist = acc.find(brand => brand.brandId === curr.brandId);
// if it does, add the item on it
if (exist) {
return acc.map((brand) => {
if (brand.brandId === exist.brandId) {
return {
...brand,
item: brand.item.concat(curr.item),
}
}
})
}
// if it doesnt, add it on accumulator, and make the item array
return acc.concat({
...curr,
item: [
curr.item
]
})
})
(I wrote the code manually and not tested)
You can simply achieve this result using Map
let data = [
{
brandId: "12345",
brand: "Adidas",
item: {
name: "Adidas 1",
price: "200",
},
},
{
brandId: "12345",
brand: "Adidas",
item: {
name: "Adidas 2",
price: "230",
},
},
{
brandId: "7878",
brand: "Nike",
item: {
name: "Nike 1",
price: "305",
},
},
];
const dict = new Map();
data.forEach((o) => {
dict.get(o.brandId)
? dict.get(o.brandId).item.push(o.item)
: dict.set(o.brandId, { ...o, item: [o.item] });
});
const result = [];
for (let [k, v] of dict) {
v.item.length === 1 ? result.push({ ...v, item: v.item[0] }) : result.push(v);
}
console.log(result);
/* This is not a part of answer. It is just to give the output fill height. So IGNORE IT */
.as-console-wrapper { max-height: 100% !important; top: 0; }

Javascript - Best/fastest way to add element to query result with fixed value

I have a node.js mysql query:
connection.query("SELECT id AS id, name AS label, status AS status from table;", function(err, rows) {
...
});
The result I'm getting back locks like this:
console.log(getBody)
[ { id: '1',
label: 'Name 01',
status: 'ACTIVE' },
{ id: '2',
label: 'Name 02',
status: 'INACTIVE' },
{ id: '3',
label: 'Name 03',
status: 'ACTIVE' },
{ id: '4',
label: 'Name 04',
status: 'ACTIVE' }];
To further cosume the result ... I need an additional paremeter 'type' with with a fixed value in the array. So result should look like this:
[ { id: '1',
label: 'Name 01',
status: 'ACTIVE',
type: 'ABC' },
{ id: '2',
label: 'Name 02',
status: 'INACTIVE',
type: 'ABC' },
{ id: '3',
label: 'Name 03',
status: 'ACTIVE',
type: 'ABC' },
{ id: '4',
label: 'Name 04',
status: 'ACTIVE',
type: 'ABC' }];
What's the fastest/best way to do this? Looping over the array? How should it look like?
Use the map method:
const newArray = array1.map(element => element = {...element, ...{type: 'ABC'}});
console.log(array1); // array1 won't be changed
console.log(newArray);
Or forEach, this will modify your array:
array1.forEach(element => element.type = 'ABC');
console.log(array1);
var arr = [
{id: '1',label: 'Name 01',status: 'ACTIVE'},
{id: '2',label: 'Name 02',status: 'INACTIVE'},
{id: '3',label: 'Name 03',status: 'ACTIVE'},
{id: '4',label: 'Name 04',status: 'ACTIVE'}];
var new_arr = arr.map(function(el) {
var o = Object.assign({}, el);
o. type = 'ABC';
return o;
})
console.log(arr);
console.log(new_arr);

Lodash - Group Children using N (repeated) keys for parent

I have a select from database that basically joins a master entity and a child entity, like the example below (Cars vs Parts) as snippet
And I'd like to group by all the keys for the Car part, and have an array of the parts, but including all the keys for the car and the parts. For the groupBy examples I could find, generally it uses groupBy, but it only groups one key only. I was able to achieve using a lot of workarounds, but I'm sure it is manageable (and achieve more performance) to do the same using either es6 or lodash.
Could someone help me in this matter? I've tried multiple groupBy and reduce combinations, but was not able to chain those correctly.
var data = [{id: 'car1',
name: 'name for car 1',
description: 'description for car1',
partId: 'partId1',
partName: 'partName1'},
{id: 'car1',
name: 'name for car 1',
description: 'description for car1',
partId: 'partId2',
partName: 'partName2'},
{id: 'car2',
name: 'name for car 2',
description: 'description for car2',
partId: 'partId3',
partName: 'partName3'},
{id: 'car2',
name: 'name for car 2',
description: 'description for car2',
partId: 'partId4',
partName: 'partName4'}
];
var dictionary = {};
data.forEach(function(item, index, array)
{
var masterDocument = null;
if (typeof dictionary[item.id] === 'undefined')
{
masterDocument = {
id: item.id,
name: item.name,
description: item.description,
parts: []
};
dictionary[item.id] = masterDocument;
}
else {
var masterDocument = dictionary[item.id];
}
masterDocument.parts.push({
partId: item.partId,
partName: item.partName
})
})
var asList = [];
Object.keys(dictionary).forEach((item) => {
asList.push(dictionary[item])
});
console.log(asList);
.as-console-wrapper {
min-height: 100%;
top: 0;
}
Here's the snippet with just the result I want to achieve.
[
{
"id": "car1",
"name": "name for car 1",
"description": "description for car1",
"parts": [
{
"partId": "partId1",
"partName": "partName1"
},
{
"partId": "partId2",
"partName": "partName2"
}
]
},
{
"id": "car2",
"name": "name for car 2",
"description": "description for car2",
"parts": [
{
"partId": "partId3",
"partName": "partName3"
},
{
"partId": "partId4",
"partName": "partName4"
}
]
}
]
The code below should solve your problem using Lodash. Basically what you want to do is:
Group the cars by id
Once you have the cars grouped by their IDs, iterate over that top-level array with a map call, and grab the id, name, and description from the first entry (since you know these are all the same for all cars in this group). Save these for later for your return object
Then, while still in this top-level map iteration, also iterate over the individual cars in each carGrouping (a nested map) to get their partId and partName, and put those into a parts array
Finally, get all of your object attributes, put them all into a return object in your top-level map call, and return them all back
Don't forget to call valueOf() at the end of your chain to get the Lodash sequence to fire
let data = [{id: 'car1',
name: 'name for car 1',
description: 'description for car1',
partId: 'partId1',
partName: 'partName1'},
{id: 'car1',
name: 'name for car 1',
description: 'description for car1',
partId: 'partId2',
partName: 'partName2'},
{id: 'car2',
name: 'name for car 2',
description: 'description for car2',
partId: 'partId3',
partName: 'partName3'},
{id: 'car2',
name: 'name for car 2',
description: 'description for car2',
partId: 'partId4',
partName: 'partName4'}
];
const carsInfo = _(data)
.groupBy('id')
.map(carGrouping => {
// all cars in this array have the same id, name, description, so just grab them from the first one
const firstCarInGroup = _.first(carGrouping);
const id = firstCarInGroup.id;
const name = firstCarInGroup.name;
const description = firstCarInGroup.description;
// do a nested map call to iterate over each car in the carGrouping, and grab their partId and partName, and return it in an object
const parts = _.map(carGrouping, car => {
return {
partId: car.partId,
partName: car.partName
}
});
return {
id,
name,
description,
parts
}
})
.valueOf();
console.log(carsInfo);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>
This one uses no dependencies. Just plain ES6+.
const data = [{
id: 'car1',
name: 'name for car 1',
description: 'description for car1',
partId: 'partId1',
partName: 'partName1'
},
{
id: 'car1',
name: 'name for car 1',
description: 'description for car1',
partId: 'partId2',
partName: 'partName2'
},
{
id: 'car2',
name: 'name for car 2',
description: 'description for car2',
partId: 'partId3',
partName: 'partName3'
},
{
id: 'car2',
name: 'name for car 2',
description: 'description for car2',
partId: 'partId4',
partName: 'partName4'
}
];
const nested = data.reduce((acc, part) => {
let index = acc.findIndex(car => car.id === part.id)
const { partId, partName, ...car } = part
if (index === -1) {
acc.push({
...car,
parts: [],
})
index = acc.length - 1
}
acc[index].parts.push({
partId,
partName,
})
return acc
}, [])
console.log(JSON.stringify(nested, null, ' '));

How to iterate through an array of object properties, in an array of objects

I have an array of objects, that looks like this:
data = [
{
title: 'John Doe',
departments: [
{ name: 'Marketing', slug: 'marketing'},
{ name: 'Sales', slug: 'sales'},
{ name: 'Administration', slug: 'administration'},
]
},
{
title: 'John Doe Junior',
departments: [
{ name: 'Operations', slug: 'operations'},
{ name: 'Sales', slug: 'sales'},
]
},
{
title: 'Rick Stone',
departments: [
{ name: 'Operations', slug: 'operations'},
{ name: 'Marketing', slug: 'marketin'},
]
},
]
How can I iterate over each object's departments array and create new arrays where I would have employees sorted by departments, so that the end result would like this:
operations = [
{
title: 'John Doe Junior',
departments: [
{ name: 'Operations', slug: 'operations'},
{ name: 'Sales', slug: 'sales'},
]
},
{
title: 'Rick Stone',
departments: [
{ name: 'Operations', slug: 'operations'},
{ name: 'Marketing', slug: 'marketin'},
]
},
]
marketing = [
{
title: 'John Doe',
departments: [
{ name: 'Marketing', slug: 'marketing'},
{ name: 'Sales', slug: 'sales'},
{ name: 'Administration', slug: 'administration'},
]
},
{
title: 'Rick Stone',
departments: [
{ name: 'Operations', slug: 'operations'},
{ name: 'Marketing', slug: 'marketin'},
]
},
]
What would be the way to create dynamically this kind of arrays?
Update
I have tried to come up with a solution using the suggestion from the answer, where I would dynamically create an array with department objects that would have an array of employees:
const isInDepartment = departmentToCheck => employer => employer.departments.find(department => department.slug == departmentToCheck);
var departments = [];
function check(departments, name) {
return departments.some(object => name === object.department);
}
employees.forEach((employee) => {
employee.departments.forEach((department) => {
let found = check(departments, department.slug);
if (!found) {
departments.push({ department: department.slug });
}
});
});
departments.forEach((department) => {
// push an array of employees to each department
//employees.filter(isInDepartment(department));
});
But, I don't know how can I push the array of employees to the object in the array that I am looping at the end?
This is the fiddle.
How about this? I use Array.protoype.filter operation, and I use a higher-order function (in this case a function that returns a function) to create the predicate (function that returns a boolean) that will check whether an employee is in a specific department. I added some (hopefully) clarifying comments in the code.
Edit: with the new code and context you provided this JSFiddle demo shows how it would work together.
const employees = [
{
title: 'John Doe',
departments: [
{ name: 'Marketing', slug: 'marketing'},
{ name: 'Sales', slug: 'sales'},
{ name: 'Administration', slug: 'administration'}
]
},
{
title: 'John Doe Junior',
departments: [
{ name: 'Operations', slug: 'operations'},
{ name: 'Sales', slug: 'sales'}
]
},
{
title: 'Rick Stone',
departments: [
{ name: 'Operations', slug: 'operations'},
{ name: 'Marketing', slug: 'marketin'}
]
}
];
// given a department, this returns a function that checks
// whether an employee is in the specified department
// NOTE: the "find" returns the found object (truthy)
// or undefined (falsy) if no match was found.
const isInDepartment =
departmentToCheck =>
employee => employee.departments.find(dep => dep.name == departmentToCheck);
const employeesInMarketing = employees.filter(isInDepartment('Marketing'));
const employeesInOperations = employees.filter(isInDepartment('Operations'));
console.log('Employees in marketing', employeesInMarketing);
console.log('Employees in operations', employeesInOperations);

Categories