Use lodash groupBy function to categorize objects in an array - javascript

i have an array of products that each product has a category object. I need to organize by category and include the category object. GroupBy function include only one parameter.
the array of products
const data = [
{id: 1, 'name': 'produto1', category: {id: 1, name: 'shirts', description: 'super roupa'}},
{id: 2, 'name': 'produto2', category: {id: 1, name: 'shirts', description: 'super roupa'}},
{id: 3, 'name': 'produto3', category: {id: 2, name: 'jackets', description: 'super jackets'}},
{id: 4, 'name': 'produto4', category: {id: 2, name: 'jackets', description: 'super jackets'}},
]
expected result:
[
{
category: {id: 1, name: 'clothes', description: 'super roupa'},
products:[{id:1, name: 'produt1'}, {id: 2, name: 'produto1'} ]
},
{
category: {id: 2, name: 'jackets', description: 'super jackets'},
products:[{id:3, name: 'produt3'}, {id: 4, name: 'produto4'} ]
},
]

Group by the category.id, and then map the each group to an object by taking the category from the 1st item in the group, and omitting category from all products:
const data = [{"id":1,"name":"produto1","category":{"id":1,"name":"shirts","description":"super roupa"}},{"id":2,"name":"produto2","category":{"id":1,"name":"shirts","description":"super roupa"}},{"id":3,"name":"produto3","category":{"id":2,"name":"jackets","description":"super jackets"}},{"id":4,"name":"produto4","category":{"id":2,"name":"jackets","description":"super jackets"}}]
const result = _(data)
.groupBy('category.id')
.map(group => ({
category: _.head(group).category,
products: _.map(group, o => _.omit(o, 'category'))
}))
.value()
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.js"></script>
Or the _.flow() function equivalent with lodash/fp:
const { flow, groupBy, map, head, omit } = _
const fn = flow(
groupBy('category.id'),
map(group => ({
category: head(group).category,
products: map(omit('category'), group)
}))
)
const data = [{"id":1,"name":"produto1","category":{"id":1,"name":"shirts","description":"super roupa"}},{"id":2,"name":"produto2","category":{"id":1,"name":"shirts","description":"super roupa"}},{"id":3,"name":"produto3","category":{"id":2,"name":"jackets","description":"super jackets"}},{"id":4,"name":"produto4","category":{"id":2,"name":"jackets","description":"super jackets"}}]
const result = fn(data)
console.log(result)
<script src='https://cdn.jsdelivr.net/g/lodash#4(lodash.min.js+lodash.fp.min.js)'></script>

Here's a solution without lodash:
You could reduce the data array. Destructure the parameter to get category and rest of the properties separately. Here rest will have id and name properties. Then create an accumulator object with each unique category's id as key. Set the value to be the final objects needed in the output. If the key already exists, update it's products array. Else, add a new key to the accumulator. Then finally use Object.values() to convert this accumulator object to an array of required values
const data = [{"id":1,"name":"produto1","category":{"id":1,"name":"shirts","description":"super roupa"}},{"id":2,"name":"produto2","category":{"id":1,"name":"shirts","description":"super roupa"}},{"id":3,"name":"produto3","category":{"id":2,"name":"jackets","description":"super jackets"}},{"id":4,"name":"produto4","category":{"id":2,"name":"jackets","description":"super jackets"}}]
const merged = data.reduce((acc, { category, ...rest }) => {
acc[category.id] = acc[category.id] || { category, products: [] };
acc[category.id].products.push(rest);
return acc;
}, {})
console.log(Object.values(merged))

Related

Looping through inner array in a filter function in JS

I'm trying to filter the array here but the result seem to be empty. How do I loop through inner array in a filter function?
Below is the snippet
const carIds = ['100', '101'];
const carsList = [{
name: 'BMW',
id: '100'
}, {
name: 'Jeep',
id: '101'
}, {
name: 'Audi',
id: '103'
}];
const result = carsList.filter((val) => val.id === carIds.map((val) => val))
console.log('result', result)
Expected output should be
[{
name: 'BMW',
id: '100'
}, {
name: 'Jeep',
id: '101'
}]
Could anyone please advise?
I'm not completely sure what you're trying to do with the .map() method, but you're not using it right. .map() applies a transformation function to each element of the array, then returns a new array of the result. See the MDN article for help with the correct usage.
In your case, you can just use the .includes() method to check if the array includes the value. Like this:
const carIds = ['100', '101'];
const carsList = [{
name: 'BMW',
id: '100'
}, {
name: 'Jeep',
id: '101'
}, {
name: 'Audi',
id: '103'
}];
const result = carsList.filter(val => carIds.includes(val.id))
console.log('result', result)
Note that in this case, it is faster to use a Set, as it can check for membership in O(1) time rather than the O(n) that an array offers. Expand the snippet below for an example:
const carIds = new Set(['100', '101']);
const carsList = [{
name: 'BMW',
id: '100'
}, {
name: 'Jeep',
id: '101'
}, {
name: 'Audi',
id: '103'
}];
const result = carsList.filter(val => carIds.has(val.id))
console.log('result', result)
I'm guessing you want to return the cars who's ID's are included in the carIds array?
If so you want to use the .includes() method instead of .map().
const result = carsList.filter((val) => carIds.includes(val.id))

How to add property value from one array of objects into another (for the matching objects) in JS

I am trying to filter array of objects based on another array of objects and after finding all matching items I want to set property and value from the first array object into corresponding object in the second array:
const searchedProducts = products.filter(product =>
uniqueProducts.some(
uniqueProduct =>
product.productId === uniqueProduct.productId,
),
)
After here I need to set product.productName for each unique product object under productName property.
Ho such a thing can be achieved in a better way?
This is probably most straightforward using reduce() combined with find() to both retrieve and verify that the second array contains each object.
const uniqueProducts = [
{id: 3, from: 'uniqueProducts', name: 'Unique 1'},
{id: 12, from: 'uniqueProducts', name: 'Unique 12'}
];
const products = [
{id: 1, from: 'products', name: 'Product 1'},
{id: 2, from: 'products', name: 'Product 2'},
{id: 9, from: 'products', name: 'Product 9'},
{id: 12, from: 'products', name: 'Product 12'},
];
const output = products.reduce((a, p) => {
const uP = uniqueProducts.find(u => u.id === p.id);
if (uP) a.push({...p, name: uP.name});
return a;
}, []);
console.log(output);
I have inventory where I have to add a price property and take its value from products array so I did this,
inventory = [
{
"productId": 1,
"quantity": 100,
"name": "2 Yolks Noodles",
"image": "twoeggnoodles.jpg",
}
]
Products = [
{
"id": 1,
"quantity": 100,
"name": "2 Yolks Noodles",
"image": "twoeggnoodles.jpg",
"price": 34.95
}
]
let product:any = [];
products.map((prod:any)=>{
const index:any = inventory.find((u:any) => u.productId === prod.id);
// console.log("item", index, '-', prod)
if(index){
product.push({...index, price: prod.price});
}
return prod
})
});

How can I optimally group a list of objects by their sub object?

I'm trying to group some JavasScript objects by their shared similar object. I can do this effortlessly in Ruby, but for the life of my I (somewhat embarrassingly) can't figure this out in JS in linear time. JS doesn't seem to allow object literals as keys, at least for the purposes of reducing.
I have data shaped like this, as a result from a GraphQL query:
[
{
id: 1,
name: 'Bob',
room: {
id: 5,
name: 'Kitchen'
}
},
{
id: 3,
name: 'Sheila',
room: {
id: 5,
name: 'Kitchen'
}
},
{
id: 2,
name: 'Tom',
room: {
id: 3,
name: 'Bathroom'
}
}
]
In the UI, we're going to display the objects by the room they're in. We need to keep a reference to the room itself, otherwise we'd just sort by a room property.
What I'm trying to do is reshape the data into something like this:
{
{id: 5, name: 'Kitchen'}: [{id: 1, name: 'Bob'}, {id: 3, name: 'Sheila'}],
{id: 3, name: 'Bathroom'}: [{id: 2, name: 'Tom'}]
}
As you can see, the people are grouped together by the room they're in.
It could also be shaped like this...
[
{ room: {id: 5, name: 'Kitchen'}, people: [{id: 1, name: 'Bob', ...}] },
{ room: {id: 3, name: 'Bathroom', people: [{id: 2, name: 'Tom'}]
]
However it comes out, we just need the people grouped by the rooms in linear time.
I've tried lodash's groupBy, using both map and reduce, just doing for loops that put the list together, etc. I'm stumped because without being able to use an object literal (the room) as a hash index, I don't know how to efficiently group the outer objects by the inner objects.
Any help is greatly appreciated.
Update: adding clarity about trying to do it with linear time complexity - the most efficient equivalent of this Ruby code:
h = Hash.new { |h, k| h[k] = [] }
value.each_with_object(h) { |v, m| m[v[:room]] << v }
You can solve this using lodash#groupBy and lodash#map to gather and transform each group. Additionally, we use lodash#omit to remove the room object from each person from the people array.
var result = _(data)
.groupBy('room.id')
.map(people => ({
room: { ...people[0].room },
people: _.map(people, person => _.omit(person, 'room'))
})).value();
var data = [
{
id: 1,
name: 'Bob',
room: {
id: 5,
name: 'Kitchen'
}
},
{
id: 3,
name: 'Sheila',
room: {
id: 5,
name: 'Kitchen'
}
},
{
id: 2,
name: 'Tom',
room: {
id: 3,
name: 'Bathroom'
}
}
];
var result = _(data)
.groupBy('room.id')
.map(people => ({
// make sure to create a new room object reference
// to avoid mutability
room: { ...people[0].room },
people: _.map(people, person => _.omit(person, 'room'))
})).value();
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>
You can use reduce to create an object of people indexed by rooms and then get that object's values, no library needed:
const input=[{id:1,name:'Bob',room:{id:5,name:'Kitchen'}},{id:3,name:'Sheila',room:{id:5,name:'Kitchen'}},{id:2,name:'Tom',room:{id:3,name:'Bathroom'}}]
const output = Object.values(
input.reduce((a, { id, name, room }) => {
const roomName = room.name;
if (!a[roomName]) a[roomName] = { room, people: [] };
a[roomName].people.push({ id, name });
return a;
}, {})
);
console.log(output);
Objects like
{id: 5, name: 'Kitchen'}: [{id: 1, name: 'Bob'}, {id: 3, name: 'Sheila'}],
in your question can't be properties like that unless the structure is a Map. Ordinary Javascript objects can only have string (/ number) properties.
One alternative is to use reduce in order to groupBy the rooms.
const input = [{
id: 1,
name: 'Bob',
room: {
id: 5,
name: 'Kitchen'
}
},
{
id: 3,
name: 'Sheila',
room: {
id: 5,
name: 'Kitchen'
}
},
{
id: 2,
name: 'Tom',
room: {
id: 3,
name: 'Bathroom'
}
}
];
const res = input
.map(person => ({
person: {
id: person.id,
name: person.name
},
room: person.room
}))
.reduce((rooms, person) => {
const room = rooms.find(room => room.id === person.room.id) ||
{ room: person.room };
const idx = rooms.indexOf(room);
room.people = room.people ?
[...room.people, person.person] :
[person.person];
return Object.assign(rooms, {
[idx === -1 ? rooms.length : idx]: room
});
}, []);
console.log(res);

Javascript how to filter an array using forEach() inside filter()

I have an array of objects and I'd like to filter it based on the objects property values. I'd like to filter it by different properties, so it needed to be dynamic. For this I have an input field where I type and then filter the array. So, let's say I have these 2 different arrays:
const array_one = [
{id: 1, code: 'ABC123', name: 'John'},
{id: 2, code: 'DEF456', name: 'Stew'},
// ...
];
const array_two = [
{id: 1, value: '012345', company: 'Company 01' },
{id: 2, value: '678910', company: 'Company 02' },
// ...
];
I want a function where I can filter the first array based on the name, also If I want to filter the second array, I want to filter it by the value.
For this, I built this function:
filterArray(array: Array<any>, fields: Array<any>, value: string) {
value = this.convertString(value);
array = array.filter((item) => {
fields.forEach(obj => {
if ( item[obj] ) {
const _newObj = this.convertString(item[obj]);
if ( _newObj.indexOf(value) !== -1 ) {
console.log(item);
return item;
}
}
});
});
return array;
}
// convertString() is just another function to replace accents, spaces, etc...
Then I call it like this:
filterArray(originalArray, ['name'], valueFromInput);
// or...
filterArray(originalArray, ['value'], valueFromInput);
// or even...
filterArray(originalArray, ['value', 'company'], valueFromInput);
But the array filtered is always returnin empty, even if the console inside the indexOf verification prints the correct object on the console.
What am I doing wrong here? Because it's filtering properly, I have manually checked it, but it doesn't add to the new filtered array.
You can iterate the fields using Array#some, and if one of them is equal to value return the item:
const array_one = [
{id: 1, code: 'ABC123', name: 'John'},
{id: 2, code: 'DEF456', name: 'Stew'}
];
const array_two = [
{id: 1, value: '012345', company: 'Company 01' },
{id: 2, value: '678910', company: 'Company 02' }
];
const filterArray = (array, fields, value) => {
fields = Array.isArray(fields) ? fields : [fields];
return array.filter((item) => fields.some((field) => item[field] === value));
};
console.log(filterArray(array_one, 'name', 'Stew'));
console.log(filterArray(array_two, ['id', 'company'], 2));
console.log(filterArray(array_two, ['id', 'company'], 'Company 02'));
If you want to make the filter about more than one filed then the value that you send it to the function, should be also array.
In my code below I assume that you want to return the object that Achieves all conditions (contains all properties that you send to the function with the same value)
const array_one = [
{id: 1, code: 'ABC123', name: 'John'},
{id: 2, code: 'DEF456', name: 'Stew'},
];
const array_two = [
{id: 1, value: '012345', company: 'Company 01' },
{id: 2, value: '678910', company: 'Company 02' },
];
function filterArray(array, fields, value) {
array = array.filter((item) => {
const found = fields.every((field, index) => {
return item[field] && item[field] === value[index]
})
return found
});
return array;
}
console.log(filterArray(array_one, ['name'], ['Stew']));
console.log(filterArray(array_two, ['id', 'company'], [1,'Company 01']));

Assign First Element of Nested Array as Property of Parent Object

Assuming the following Array:
[
{id: 1234, name: "#Acme", sources:["Twitter"]},
{id: 5678, name: "#Enron", sources:["Facebook"]},
]
I want to promote sources[0] to a property value, either under sources itself or as a new key using lodash.
I've done the following:
myList = _.map(monitorList, _.partialRight(_.pick, ['id', 'name', 'sources']));
mySources = _.map(monitorList, 'sources');
I imagine I can iterate through each respective array now and map my index from mySources to the sources key in myList, however it seems like there should be a functional way using lodash to promote a nested array item to a property value.
Ideal final data structure:
[
{id: 1234, name: "#Acme", sources:"Twitter"},
{id: 5678, name: "#Enron", sources:"Facebook"},
]
With a functional ES6 approach:
const monitorList = [
{id: 1234, name: "#Acme", sources:["Twitter"]},
{id: 5678, name: "#Enron", sources:["Facebook"]},
];
var result = monitorList.map(o => Object.assign({}, o, { sources: o.sources[0] }));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can follow a simple path and use forEach to replace sources property:
var items = [{id: 1234, name: "#Acme", sources:["Twitter"]},
{id: 5678, name: "#Enron", sources:["Facebook"]}];
items.forEach((item) => item.sources = item.sources[0]);
console.log(items);
Another solution, using map, which is more functional (as it does not change items variable):
var items = [{id: 1234, name: "#Acme", sources:["Twitter"]},
{id: 5678, name: "#Enron", sources:["Facebook"]}];
var newItems = items.map((item) => Object.assign({}, item, { sources: item.sources[0] }));
console.log(newItems);
You could use map:
var array = [{id: 1234, name: "#Acme", sources:["Twitter"]},
{id: 5678, name: "#Enron", sources:["Facebook"]}];
var items = array.map(item => {
item.source = item.sources[0];
return item;
});
You could change item.source to item.sources as well if you wanted to overwrite.
Another way using some losdash methods:
var items = array.map(item => {
return _.assign({}, item, {sources: _.first(item.sources)});
});

Categories

Resources