Related
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
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))
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.
EDIT: Upon looking further, something is calling toString on the array being returned from the model hook. Hard-coded, this method works fine, but being returned like I have below, it doesn't.
TL;DR: I'm manipulating one array of data to a different "format" so that I can use the Ember Charts add on. To do this, I'm using Ember.RSVP in the model hook of my route, but I'm getting a TypeError: Cannot convert object to primitive value and don't know why.
I've been learning Ember (followed their quickstart and then super-rentals tutorial), and I am now trying to build my own project with it.
The issue that I'm having is dealing with async operations within the model hook of my route. This may be a long read, but I'm trying to be very specific. The end problem, however, is that I'm getting a TypeError: Cannot convert object to primitive value at the end of it all.
I'm using Mirage to fake a backend API, and it returns my data like this:
{
"data": [
{
"id": "May, 2017",
"type": "month",
"attributes": {
"label": "05/2017",
"value": 6
}
}, {
"id": "April, 2017",
"type": "month",
"attributes": {
"label": "04/2017",
"value": 5
}
}, {
"id": "March, 2017",
"type": "month",
"attributes": {
"label": "03/2017",
"value": 4
}
}, {
"id": "February, 2017",
"type": "month",
"attributes": {
"label": "02/2017",
"value": 3
}
}, {
"id": "January, 2017",
"type": "month",
"attributes": {
"label": "01/2017",
"value": 2
}
}, {
"id": "December, 2016",
"type": "month",
"attributes": {
"label": "12/2016",
"value": 1
}
}
]
};
I'm also using Ember Charts from Addepar (https://github.com/Addepar/ember-charts), which requires data that looks like this (group label is optional and I am not using it for my app, but this is taken from Addepar's docs):
[{
"label": "Label 1",
"group": "Group One",
"value": 20
},
{
"label": "Label 2",
"group": "Group One",
"value": 22
},
{
"label": "Label 3",
"group": "Group One",
"value": 18
},
{
"label": "Label 4",
"group": "Group One",
"value": 2
},
{
"label": "Label 5",
"group": "Group One",
"value": 6
},
{
"label": "Label 1",
"group": "Group Two",
"value": 26
},
{
"label": "Label 2",
"group": "Group Two",
"value": 18
},
{
"label": "Label 3",
"group": "Group Two",
"value": 150
},
{
"label": "Label 4",
"group": "Group Two",
"value": 160
},
{
"label": "Label 5",
"group": "Group Two",
"value": 200
},
{
"label": "Label 1",
"group": "Group Three",
"value": 14
},
{
"label": "Label 2",
"group": "Group Three",
"value": 31
},
{
"label": "Label 3",
"group": "Group Three",
"value": 44
},
{
"label": "Label 4",
"group": "Group Three",
"value": 30
},
{
"label": "Label 5",
"group": "Group Three",
"value": 62
},
{
"label": "Label 1",
"group": "Group Four",
"value": 75
},
{
"label": "Label 2",
"group": "Group Four",
"value": 114
},
{
"label": "Label 3",
"group": "Group Four",
"value": 19
},
{
"label": "Label 4",
"group": "Group Four",
"value": 129
},
{
"label": "Label 5",
"group": "Group Four",
"value": 52
},
{
"label": "Label 1",
"group": "Group Five",
"value": 200
},
{
"label": "Label 2",
"group": "Group Five",
"value": 14
},
{
"label": "Label 3",
"group": "Group Five",
"value": 31
},
{
"label": "Label 4",
"group": "Group Five",
"value": 44
},
{
"label": "Label 5",
"group": "Group Five",
"value": 30
}]
Basically, because I'm following the JSONAPI specs, the data I'm receiving from the API looks different than what needs to be passed into the Ember Charts Vertical Bar Graph component.
My solution so far (which isn't working) is to loop through the initial data and populate a new array that looks the way Ember Charts needs it to. As far as I understand, this can only happen asynchronously, because I have to make an API call (well, a call to my store), and then operate on the results that I get before returning the new array from the model hook.
Here's the actual code that I'm using right now:
import Ember from 'ember';
export default Ember.Route.extend({
model() {
var results = this.get('store').findAll('month');
var months = results.then(function(data) {
var monthsArray = [];
data.content.forEach(month => {
monthsArray.push(month.__data);
});
return Ember.RSVP.all(monthsArray);
});
return Ember.RSVP.hash({
data: months
});
}
});
This gets accessed in my template like this (the component is provided by the Ember Charts addon) {{vertical-bar-chart data=model.data}}.
Like stated at the beginning, I get a TypeError: Cannot convert object to primitive value. My only lead as to where it's coming from is the fact that if I hard-code the data (in the second, correct format) and return it instead of pulling it from Mirage, the bar graph populates perfectly fine. But if I do it the way I have above, I get the error. I used the afterModel hook to console.log(resolvedModel), and the data is there either way. Maybe that's not a good indicator though.
Anyway, I'm super green with Ember and I may be suffering from just misunderstanding all of this stuff. Any help is appreciated. Sorry for the long post.
Why do you need RSVP.all and RSVP.hash ?. I am missing something from your requirement.
May be you can give the below code one attempt,
model() {
return this.get('store').findAll('month').then(data => {
var monthsArray = [];
data.content.forEach(month => {
monthsArray.push(month.__data);
});
return monthsArray;
});
}
and temlate you need to access it like model
EDIT: the answer still lies with JSON.parse(JSON.stringify(data)), but I updated with kumkanillam's much simpler approach returning the data.
It turned out the error was coming from Array.toString being called on the data being returned from the model hook. It worked fine on a hard-coded array of objects, but not when returned via RSVP as I have in my question.
Upon inspecting both, it turned out, when using Ember.RSVP, the objects in the returned array lacked a toString method on them, somehow... I tried solving this via setting the prototype with Object.create(), but didn't work.
Bottom line is, and I'm still not 100% clear, I thought maybe Ember was turning my array into something else (an Ember Object?), not just a plain JS array of objects. So I ended up converting the returned data into a plain JS object again, via this answer.
Route now looks like this:
model() {
// Fetch month data from store
return this.get('store').findAll('month').then(data => {
var monthsArray = [];
data.content.forEach(month => {
monthsArray.push(month.__data);
});
// Must convert into normal JS object to avoid TypeError: cannot convert object into primitive value
return JSON.parse(JSON.stringify(monthsArray));
});
}
The JSON.parse(JSON.stringify(data)) basically turns the data into a string and then converts it back into a JS object. It feels a little dirty, but after 5 hours of working on this same thing, it's the only thing that worked!
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