Group by property of an object within a nested array - javascript

How would you group an array by a property of the objects in a nested array. Either vanilla or lodash.
For example, how would you group an array of tasks by the assignee, when tasks can have more than one person assigned.
Example data:
[
{
"assignees": [
{
"name": "John",
"uid": "id0001"
},
{
"name": "Sally",
"uid": "id0002"
}
],
"title": "Task 1",
"status": "To do"
},
{
"assignees": [
{
"name": "John",
"uid": "id0001"
}
],
"title": "Task 2",
"status": "To do"
},
{
"assignees": [
{
"name": "Sally",
"uid": "id0002"
}
],
"title": "Task 3",
"status": "To do"
}
]
Example desired result:
{
"id0001": {
"name": "John",
"tasks": [
{
"title": "Task 1",
"status": "To do"
},
{
"title": "Task 2",
"status": "To do"
}
]
}
},
{
"id0002": {
"name": "Sally",
"tasks": [
{
"title": "Task 1",
"status": "To do"
},
{
"title": "Task 3",
"status": "To do"
}
]
}
}
I have a lot of things both vanilla and lodash - a simple example is below. I was expecting an array of three groups. One for Sally, one for John and one for Sally and John:
const grouped = _.groupBy(taskList, (task) => task.assignees);
But that returns one group [object Object] with all the tasks in it.

Since uids are unique, the Array seems like an overhead.
I suggest to get an Object literal with the tasks grouped by the user's uids: like:
{
id0001: {...user, tasks: []},
id0002: {...user, tasks: []},
}
Example:
const groupTasksByUserId = (arr) => arr.reduce((ob, {assignees, ...task}) => {
assignees?.forEach((user) => {
ob[user.uid] ??= {...user, tasks: []};
ob[user.uid].tasks.push(task)
});
return ob;
}, {});
const data = [{
"assignees": [{"name": "John", "uid": "id0001"}, {"name": "Sally","uid": "id0002"}],
"title": "Task 1",
"status": "To do"
}, {
"assignees": [{"name": "John", "uid": "id0001"}],
"title": "Task 2",
"status": "To do"
}, {
"assignees": [{"name": "Sally", "uid": "id0002"}],
"title": "Task 3",
"status": "To do"
}];
const res = groupTasksByUserId(data);
// console.log(res);
console.log(JSON.stringify(res, 0, 2));
Find out more about the above used Array methods (reduce and forEach) on MDN Docs

Related

a function for data filtration based on two arrays of strings in Javascript

I need some help with array filtration. I want to filter an array of objects based on:
Note: these arrays can be empty. When they are empty, the function should return the original array (without data filtration)
brands: ["brand 1", "brand 2", "brand 3", "brand 4"],
tags: ["tag1", "tag2", "tag 3", "tag 4"],
The array of objects which I want to do filter looks like this:
[
{
"tags": [
"tag1"
],
"price": 10.99,
"name": "Sample name",
"manufacturer": "brand 1",
},
{
"tags": [
"tag1", "tag2"
],
"price": 10.99,
"name": "Sample name",
"manufacturer": "brand 1",
},
{
"tags": [
"tag1", "tag3", "tag4"
],
"price": 10.99,
"name": "Sample name",
"manufacturer": "brand 4",
},
{
"tags": [
"tag1", "tag2"
],
"price": 10.99,
"name": "Sample name",
"manufacturer": "brand1 ",
},
]
the function I have looks like this doing filtration on the manufacturer only:
const obj = {
brands: ["brand 1", "brand 2"],
tags: ["tag1", "tag2", "tag 4"],
}
const filterArray = (obj, array) => {
let newArray = []
const byBrands = array.filter((item) =>
obj.brands.includes(item["manufacturer"].toLowerCase())
)
if (byBrands.length > 0) {
newArray = byBrands
} else {
newArray = array
}
return newArray;
}
I need a function to do filtration on tags and manufacturer at the same time.
Thanks, Stakoverflow :)
You should use keys in your filter object that match properties in the objects to be filtered, otherwise there's no way to know which property compare. Other than that it's just a matter of checking if every() property in the filter object has some() matching entry in the object being filtered. The example ignores empty arrays in the filter object using an OR (||) short-circuit, and uses concat() to evaluate every property as an array.
(You can fine tune to make it case-insensitive, search for substrings, etc.)
const input = [{ "tags": ["tag1"], "price": 10.99, "name": "Sample name", "manufacturer": "brand 1", }, { "tags": ["tag1", "tag2"], "price": 10.99, "name": "Sample name", "manufacturer": "brand 1", }, { "tags": ["tag 4"], "price": 10.99, "name": "Sample name", "manufacturer": "brand 2", }, { "tags": ["tag 3"], "price": 10.99, "name": "Sample name", "manufacturer": "brand 1", },]
const obj = {
manufacturer: ["brand 1", "brand 2"],
tags: ["tag1", "tag2", "tag 4"],
name: [], // ignores empty arrays
}
function filterProducts(array, filters) {
return array.filter(p =>
Object.entries(filters).every(([k, fs]) =>
!fs.length || fs.some(f => [].concat(p[k]).some(t => t === f)))
)
}
console.log(filterProducts(input, obj))

Using Map() function to pull out double nested JSON data with ReactJS

I'm trying to pull out the individual images in the "images" object below under "details" section.
Seem to just be getting nothing printing out. Looking for the correct way to pull within the details.images.image1,2, or 3.
Here is the JSON data I'm working with so far:
{
"books": [
{
"title": "title 1",
"image": "/image1.jpg"
},
{
"title": "title 2",
"image": "/image2.jpg"
}
],
"details": [
{
"author": "book author",
"name": "Book name",
"price": 34.99,
"publisher": "Penguin Books",
"images": [
{
"image1": "/image1.jpg",
"image2": "/image2.jpg",
"image3": "/image3.jpg"
}
]
}
]
}
Also here is the JSON call I'm making in a Book component:
{staticdata.details.map(detail => (
<Book
book_name={detail.author}
book_price={detail.price}
image={detail.images.image1}
/>
))}
Here's an example of accessing those nested properties and logging them to the console. It appears your attempt was mostly correct, but images is an array.
const data = {
"books": [
{
"title": "title 1",
"image": "/image1.jpg"
},
{
"title": "title 2",
"image": "/image2.jpg"
}
],
"details": [
{
"author": "book author",
"name": "Book name",
"price": 34.99,
"publisher": "Penguin Books",
"images": [
{
"image1": "/image1.jpg",
"image2": "/image2.jpg",
"image3": "/image3.jpg"
}
]
}
]
}
data.details.map(detail => {
console.log(detail.author, detail.price, detail.images[0].image1);
});

Filter array on multiple attributes on same key

I have a json and would like to filter for one key multiple attribites as exact match.
I tried the following:
let data = [{
"name": "Product 2",
"link": "/stock/product2",
"category": "234",
"description": ""
}, {
"name": "Product 1",
"link": "/stock/product1",
"category": "1231",
"description": ""
}, {
"name": "Product 3",
"link": null,
"category": "22",
"description": ""
}]
data = data.filter(cv => cv.category === ["22", "234"]);
console.log(JSON.stringify(data))
I would like to get back the object with the name: Product 2 and name: Product 3.
Any suggestions why I get [] back?
I appreciate your replies!
Consider using Set.has for your desired attributes, so you can can have O(1) lookup time rather than the O(n) (where n is the number of desired attributes) lookup time using Array.includes.
As a result, if you use a set the overall the time complexity for the whole filter line will be O(m) (where m is the number of objects in data) rather than O(mn) if you used Array.includes or had multiple if-else / or conditions to check for each desired attribute:
const data = [
{
name: "Product 2",
link: "/stock/product2",
category: "234",
description: "",
},
{
name: "Product 1",
link: "/stock/product1",
category: "1231",
description: "",
},
{
name: "Product 3",
link: null,
category: "22",
description: "",
},
];
const desiredCategories = new Set(["22", "234"]);
const filteredData = data.filter(cv => desiredCategories.has(cv.category));
console.log(JSON.stringify(filteredData, null, 2));
You are comparing a single value against an array of values. One solution would be to check for one value or (||) the other.
data = data.filter(cv => cv.category === "22" || cv.category === "234");
This can be achieved by the includes method.
let data = [{
"name": "Product 2",
"link": "/stock/product2",
"category": "234",
"description": ""
}, {
"name": "Product 1",
"link": "/stock/product1",
"category": "1231",
"description": ""
}, {
"name": "Product 3",
"link": null,
"category": "22",
"description": ""
}]
data = data.filter(cv => ["22", "234"].includes(cv.category));
console.log(JSON.stringify(data))
Besides, I think this is easy to read/understand.
Check if the item is in the array instead
data = data.filter(cv => ["22", "234"].includes(cv.category));
const data = [{
"name": "Product 2",
"link": "/stock/product2",
"category": "234",
"description": ""
}, {
"name": "Product 1",
"link": "/stock/product1",
"category": "1231",
"description": ""
}, {
"name": "Product 3",
"link": null,
"category": "22",
"description": ""
}]
const filteredData = data.filter(({ category }) => category === "22" || category === "234");
console.log(JSON.stringify(filteredData))
I wouldn't mutate your original object. Also, you can deconstruct category in the filter function.

Filtering JSON with underscore

Given a data structure like so:
"items":
{
"Groups":[
{
"title":"group 1",
"SubGroups":[
{
"title":"sub1",
"id" : "1",
"items":[
{
"title":"Ajax request 1",
},
{
"title":"Ajax request 2",
}
]
},
{
"title":"sub2",
"id" : "2",
"items":[
{
"title":"Ajax request 3",
},
{
"title":"Ajax request 4",
}
]
}
]
}
]
How can I pull out all the items for a sub group based on the id? Ive treid using find like so:
var res1 = _.where(listing.items,{id:"2"});
but get an empty array returned
Thanks
Try targeting the subgroups array and then search for the id you want in there. That should then return the properties for that subgroup.
var obj = {
"Groups": [{
"title": "group 1",
"SubGroups": [{
"title": "sub1",
"id": "1",
"items": [{
"title": "Ajax request 1",
}, {
"title": "Ajax request 2",
}]
}, {
"title": "sub2",
"id": "2",
"items": [{
"title": "Ajax request 3",
}, {
"title": "Ajax request 4",
}]
}]
}]
}
then find values like this
_.where(obj.Groups[0].SubGroups, {
'id': '2'
});
Just tested and this seems to work

Constructing multidimensional array out of other arrays

I’m using an external service to pull the events of an organisation (GetEvents). Example of what is returned:
{
"Result":"success",
"Key":"12345",
"Data":[
{"ID":"GFDCV34","lastChangedDate":"2015-12-03 11:14:27"},
{"ID":"IDJHE23","lastChangedDate":"2015-12-03 15:17:47"},
{"ID":"KDJBD34","lastChangedDate":"2015-12-03 05:25:11"}
]
}
Next, I can pull details of a certain event (GetEventDetails). The ID of the event is required as data parameter. I made a function getdetails(id); that returns the details.
For example, for getdetails('KDJBD34'), it gives me:
{
"Result":"success",
"Key":"52523",
"Data":[
{
"ID": "KDJBD34",
"name": "Name of event 3",
"date": "date of event 3",
"location": "location of event 3",
"lastChangedDate":"2015-12-03 05:25:11"
}
]
}
I want to construct an array containing all the events and their details, like this:
{
"Result": "success",
"Key": "12345",
"Data":[
{
"ID": "GFDCV34",
"name": "Name of event 1",
"date": "date of event 1",
"location": "location of event 1",
"lastChangedDate": "2015-12-03 11:14:27"
},
{
"ID": "IDJHE23",
"name": "Name of event 2",
"date": "date of event 2",
"location": "location of event 2",
"lastChangedDate": "2015-12-03 15:17:47"
},
{
"ID": "KDJBD34",
"name": "Name of event 3",
"date": "date of event 3",
"location": "location of event 3",
"lastChangedDate":"2015-12-03 05:25:11"
}
]
}
Anyone who can point me in the right direction?
You should operate through your first results and attach the new retrieved properties
var res = {
"Result": "success",
"Key": "12345",
"Data": [{
"ID": "GFDCV34",
"lastChangedDate": "2015-12-03 11:14:27"
}, {
"ID": "IDJHE23",
"lastChangedDate": "2015-12-03 15:17:47"
}, {
"ID": "KDJBD34",
"lastChangedDate": "2015-12-03 05:25:11"
}]
};
var tmp;
res.Data.map(function(val,i){
tmp = getdetails(val.ID);
Object.keys(tmp.Data[0]).map(function(v,j){
val[v] = tmp.Data[0][v];
});
});
Demo

Categories