Related
I have a filter object that is returned by query params
url = /all?channels=calls,text&calls=voicemail,missed
const query = {
channels: 'calls,texts',
calls: 'voicemail,missed',
};
I then have an array of objects that come in from a socket.
const arr = [
{
id: 1,
channel: 'SMS',
sent: '2021-08-22T03:21:18.41650+0000',
sender: {
contactType: 'business',
},
recipients: [
{
contactType: 'corporate',
},
],
direction: 'INBOUND',
},
{
id: 2,
channel: 'VOICE',
sent: '2021-08-20T23:15:56.00000+0000',
sender: {
contactType: 'business',
},
recipients: [
{
contactType: 'corporate',
},
],
callDetails: {
answered: false,
voicemail: true,
},
direction: 'INBOUND',
},
{
id: 3,
channel: 'VOICE',
sent: '2021-08-20T23:15:56.00000+0000',
sender: {
contactType: 'business',
},
recipients: [
{
contactType: 'corporate',
},
],
callDetails: {
answered: true,
voicemail: false,
},
direction: 'INBOUND',
},
{
id: 4,
channel: 'VOICE',
sent: '2021-08-20T23:15:56.00000+0000',
sender: {
contactType: 'business',
},
recipients: [
{
contactType: 'corporate',
},
],
callDetails: {
answered: false,
voicemail: false,
},
direction: 'INBOUND',
},
];
I want to filter out the objects that match the filters but the query obj isn't friendly enough to just map the arr through.
With the query obj shared above, i should return the objects id:1 and id:2 and id:4 from arr, since those object meet the criteria of sms, voicemail, & missed
I assume i need a modified query obj that has to have various conditions available for each property, i.e calls: voicemail === callDetails.voicemail === true or calls: received === callDetails.answered === true
I've seen lots of examples on how to filter an array of objects with multiple match-criteria, but with the req of the property having multiple conditions, i've hit a wall.
thanks for the help
The main idea is to provide kind of a rosetta stone which does bridge/map the query specific syntax with any list item's specific data structure. Thus one will end up writing a map which takes a query's structure into account but ensures for each necessary query endpoint an item specific filter condition/function.
The query function should simply filter the item list by applying a list of logical OR conditions, thus using some for returning the boolean filter value.
Which leaves one of implementing a helper method which collects ... via Object.entries and Array.prototype.flatMap as well as via String.prototype.split and Array.prototype.map ... the function endpoints from the above introduced requirements configuration/map, based on the query object, provided by the system. Thus this helper might be named resolveQuery.
const sampleList = [{
id: 1,
channel: 'SMS',
direction: 'INBOUND',
}, {
id: 2,
channel: 'VOICE',
callDetails: {
answered: false,
voicemail: true,
},
direction: 'INBOUND',
}, {
id: 3,
channel: 'VOICE',
callDetails: {
answered: true,
voicemail: false,
},
direction: 'INBOUND',
}, {
id: 4,
channel: 'VOICE',
callDetails: {
answered: false,
voicemail: false,
},
direction: 'INBOUND',
}];
// prepare a `requirements` map which ...
// - on one hand maps `query`-syntax to a list items's structure
// - and on the other hand does so by providing an item specific
// filter condition/function for each necessary query endpoint.
const requirements = {
channels: {
texts: item => item.channel === 'SMS',
},
calls: {
voicemail: item => item.channel === 'VOICE' && !!item.callDetails.voicemail,
missed: item => item.channel === 'VOICE' && !item.callDetails.answered,
},
}
// const query = {
// channels: 'calls,texts',
// calls: 'voicemail,missed',
// };
function resolveQuery(requirements, query) {
const reject = item => false;
// create/collect a list of filter condition/functions
// which later will be applied as logical OR via `some`.
return Object
.entries(query)
.flatMap(([ groupKey, groupValue ]) =>
// e.g groupKey => 'channels',
// groupValue => 'calls,texts'
groupValue
.split(',')
.map(requirementKey =>
// e.g requirementKey => 'calls'
// or requirementKey => 'texts'
requirements?.[groupKey]?.[requirementKey?.trim()] ?? reject
)
);
}
function queryFromItemList(itemList, requirements, query) {
const conditionList = resolveQuery(requirements, query);
console.log(
'conditionList ... [\n ',
conditionList.join(',\n '),
'\n]'
);
return itemList.filter(item =>
conditionList.some(condition => condition(item))
);
}
console.log(
queryFromItemList(sampleList, requirements, {
channels: 'calls,texts',
calls: 'voicemail,missed',
})
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
I have this structure in my Accounts Model:
{
cards: {
starter: [],
intermediate: [],
advanced: [ {Object}, {Object}, {Object} ]
},
firstName: 'Sepideh',
email: 'sepideh#example.com',
}
The Objects inside cards.advanced array above are like:
{
cards: [
{ // this is a single card object
cardTitle: 'this card is for you',
performance: [],
cardID: 32,
}
],
unit: 3 // this is the unit we want to push our new card object into
}
Assuming I can access the document above (or account in other words) with the given email:
const account = await db.Account.findOne({ email: 'sepideh#example.com' });
How can we push a new card object into the nested cards array with unit: 3 (yes so far this is the only unit) property and save Sepideh account like this:
{
cards: {
starter: [],
intermediate: [],
advanced: [
{
cards: [
{ // this is a single card object
cardTitle: 'this card is for you',
performance: [],
cardID: 32,
}
{ // this is new pushed card object
cardTitle: 'this card is for me',
performance: [],
cardID: 33,
}
],
unit: 3 // this is the unit we want to push our new card object into
},
{Object},
{Object}
] // end of advanced array
},
firstName: 'Sepideh',
email: 'sepideh#example.com',
}
I have tried to select the unit 3 of the document using these guys and none of them worked:
const userUnit = await account.findOne({'cards.advanced.unit': unit});
const userUnit = await account.findOne({'cards.advanced.unit': unit});
const userUnit = await account.find({}, {'cards.advanced.unit': {$elemMatch: {unit: unit}}});
const userUnit = await db.Account.findOne({ email: email, 'cards.advanced.unit': unit});
Then I would push my new card object to the userUnit and call await account.save();
The only thing which is working is pure JavaScript code like this to select:
const userUnit = account.cards.advanced.find(e => e.unit == unit);
This time I cannot save the changes to the database... (I don't know how to save it..)
How would you do this?
You can use the $push operator directly along with the $ positional operator which allows you to specify which sub-element should be updated:
await db.Account.update({ email: "sepideh#example.com", "cards.advanced.unit": 3 },
{ $push: { "cards.advanced.$.cards": { cardTitle: 'this card is for me', performance: [], cardID: 33 } } })
If you want your path to be evaluated dynamically you can use computed property names:
let path = "advanced";
await db.Account.update(
{ email: "sepideh#example.com", ["cards." + path + ".unit"]: 3 },
{ $push: { ["cards." + path + ".$.cards"]: { cardTitle: 'this card is for me', performance: [], cardID: 33 } } })
Mongo Playground
I have a reduce function that formats my data in the way i need but the only issue is the data is nested inside an array. I need to remove the outter array or just stop the reduce function from adding it in but every attempt Ive made to stop the reducer from wrapping the data in an array breaks my code. Ideally I would like my reducer to not wrap the data in an array but if thats not possible removing the array i need from inside the reducer cleanly seems like the only solution:
my data looks like this:
{
count: 4,
datapoints: [
{
Date: "2021-05-05",
score: 0,
},
{
Date: "2021-05-12",
score: 0,
},
{
Date: "2021-05-30",
score: 0,
},
{
Date: "2021-06-03",
score: 114,
},
],
};
my reducer function and api call:
const riskScores = await api.PCC.riskAssessment(userID, startdate, endDate);
const riskScoresFormatted = riskScores.datapoints.reduce((result, data) => {
const scores = result["riskAssessment"] || [];
scores.push({
value: data.score,
unit: "none",
recordedDate: data.Date,
method: "none",
type: "riskAssessment",
});
result["riskAssessment"] = scores;
return result;
}, []);
the output:
[riskAssessment: [{…}, {…}, {…}, {…}] ]
Ive tried just using the index of riskScoresFormatted[0] that comes back undefined. riskScoresFormatted.slice(1) just returns an empty array. Ive also tried targeting the first Item like riskScoresFormatted.riskAssessment this works but the value is sometimes null so it causes bugs later down the line.
Try changing the final reduce argument from [] to {} and I think you'll have better luck.
const riskScoresFormatted = riskScores.datapoints.reduce((result, data) => {
const scores = result["riskAssessment"] || [];
scores.push({
value: data.score,
unit: "none",
recordedDate: data.Date,
method: "none",
type: "riskAssessment",
});
result["riskAssessment"] = scores;
return result;
}, {});
Or, use Array.map() instead:
const riskScores = {
count: 4,
datapoints: [{
Date: "2021-05-05",
score: 0,
},
{
Date: "2021-05-12",
score: 0,
},
{
Date: "2021-05-30",
score: 0,
},
{
Date: "2021-06-03",
score: 114,
},
],
};
var riskScoresFormatted = riskScores.datapoints.map((data) => ({
value: data.score,
unit: "none",
recordedDate: data.Date,
method: "none",
type: "riskAssessment",
}));
console.log(riskScoresFormatted);
general programming problem here.
I have this array called SPACES
[
{
_id: 5e1c4689429a8a0decf16f69,
challengers: [
5dfa24dce9cbc0180fb60226,
5dfa26f46719311869ac1756,
5dfa270c6719311869ac1757
]
},
{
_id: 5e1c4eb9c9461510407d5e81,
challengers: [ 5dfa24dce9cbc0180fb60226, 5dfa26f46719311869ac1756 ],
}
]
And this array called USERS
[
{
_id: 5dfa24dce9cbc0180fb60226,
name: 'Account 1',
email: 'account1#gmail.com',
spaces: [ 5e1c4689429a8a0decf16f69, 5e1c4eb9c9461510407d5e81 ],
},
{
_id: 5dfa26f46719311869ac1756,
name: 'Account 2',
email: 'account2#gmail.com',
spaces: [ 5e1c4689429a8a0decf16f69, 5e1c4eb9c9461510407d5e81 ]
},
{
_id: 5dfa270c6719311869ac1757,
name: 'Account 3',
email: 'account3#gmail.com',
spaces: [ 5e1c4689429a8a0decf16f69 ]
}
]
What I want to do, is go through both, and instead of having the SPACES.challengers array be just IDS, I would like the array to contain each USER object.
So for example, if the USER has an ID that is inside the SPACES.challengers array, then push the user into that array (which will then be the entire object).
SO FAR I have tried this (I am not very good yet):
users.map( ( user ) => {
spaces.map( ( space ) => {
if ( user.spaces.includes( space._id ) ) {
space.challengers.push(user)
}
} );
} );
However, I am not getting inside the IF block. (Even if I did, not sure if it would work OR if this is even how to do it). It feels Odd doing double maps, as I get so many iterations, and it duplicates my push (cause I have no logic to see if it just has been pushed).
Assuming every entry in the Users array has a unique ID, we can build a Hashmap to store (id, index) pairs in order to search efficiently for an ID from Users array while looping through Spaces array.
let spaces = [{_id: '5e1c4689429a8a0decf16f69',challengers: ['5dfa24dce9cbc0180fb60226', '5dfa26f46719311869ac1756', '5dfa270c6719311869ac1757']},{_id: '5e1c4eb9c9461510407d5e81',challengers: [ '5dfa24dce9cbc0180fb60226', '5dfa26f46719311869ac1756' ],}]
let users = [{_id: '5dfa24dce9cbc0180fb60226',name: 'Account 1',email: 'account1#gmail.com',spaces: [ '5e1c4689429a8a0decf16f69', '5e1c4eb9c9461510407d5e81' ],},{_id: '5dfa26f46719311869ac1756',name: 'Account 2',email: 'account2#gmail.com',spaces: [ '5e1c4689429a8a0decf16f69', '5e1c4eb9c9461510407d5e81' ]},{_id: '5dfa270c6719311869ac1757',name: 'Account 3',email: 'account3#gmail.com',spaces: [ '5e1c4689429a8a0decf16f69' ]}]
let IDIndexMapping = {} // To store (_id, index) pairs, in order to improve search efficiency
for(let index in users) // Iterate through Users array using index
IDIndexMapping[users[index]._id] = index; // store (_id, index) pair in IDIndexMapping
// I'm avoiding using `map` and using vanilla `for` loop for space efficiency
// as map returns a new array but with `for` loop, we can perform changes in-place
for(let outerIndex in spaces){ // Iterate through `spaces` array using index
let challengers = spaces[outerIndex].challengers; // Get challengers array
for(let innerIndex in challengers){ // Iterate through challengers array using index
let ID = challengers[innerIndex]; // Get ID
if(ID in IDIndexMapping) // If ID exists in IDIndexMapping
spaces[outerIndex].challengers[innerIndex] = users[IDIndexMapping[ID]]; // Change ID to actual User object
}
}
console.log(spaces)
Output
[ { _id: '5e1c4689429a8a0decf16f69',
challengers: [ [Object], [Object], [Object] ] },
{ _id: '5e1c4eb9c9461510407d5e81',
challengers: [ [Object], [Object] ] } ]
.map and .find should work here. keep it simple.
var spaces = [
{
_id: "5e1c4689429a8a0decf16f69",
challengers: [
"5dfa24dce9cbc0180fb60226",
"5dfa26f46719311869ac1756",
"5dfa270c6719311869ac1757"
]
},
{
_id: "5e1c4eb9c9461510407d5e81",
challengers: ["5dfa24dce9cbc0180fb60226", "5dfa26f46719311869ac1756", "some non existent"]
}
],
users = [
{
_id: "5dfa24dce9cbc0180fb60226",
name: "Account 1",
email: "account1#gmail.com",
spaces: ["5e1c4689429a8a0decf16f69", "5e1c4eb9c9461510407d5e81"]
},
{
_id: "5dfa26f46719311869ac1756",
name: "Account 2",
email: "account2#gmail.com",
spaces: ["5e1c4689429a8a0decf16f69", "5e1c4eb9c9461510407d5e81"]
},
{
_id: "5dfa270c6719311869ac1757",
name: "Account 3",
email: "account3#gmail.com",
spaces: ["5e1c4689429a8a0decf16f69"]
}
],
result = spaces.map(({ _id, challengers }) => ({
_id,
challengers: challengers.map(challenger =>
users.find(user => user._id === challenger)
).filter(row => row)
}));
console.log(JSON.stringify(result, null, 2));
You can create a map of challengers for look-up and then put them in spaces.
//create user map for look-up
userMap = users.reduce((res, val) => ({
...res,
[val._id]: val
}), {});
//change challenger id with user object
inflatedSpaces = spaces.map(s => ({ ...s, challengers: s.challengers.map(c => userMap[c]) }));
You could map the users with a Map.
Beside the destructuring of the object for mapping this answer uses for this part
challengers: challengers.map(
Map.prototype.get, // cb with a prototype and using `this`
new Map(users.map(o => [o._id, o])) // thisArg
)
the above mentioned Map in two parts.
The lower part generates an instance of Map where _id of the users items is used as key and the whole object as value. This instance is uses as thisArg of Array#map, the second parameter.
The upper part is a prototype of Map, used as callback. And while an this object is supplied, a binding (Function#bind) is not necessary.
var spaces = [{ _id: '5e1c4689429a8a0decf16f69', challengers: ['5dfa24dce9cbc0180fb60226', '5dfa26f46719311869ac1756', '5dfa270c6719311869ac1757'] }, { _id: '5e1c4eb9c9461510407d5e81', challengers: ['5dfa24dce9cbc0180fb60226', '5dfa26f46719311869ac1756'] }],
users = [{ _id: '5dfa24dce9cbc0180fb60226', name: 'Account 1', email: 'account1#gmail.com', spaces: ['5e1c4689429a8a0decf16f69', '5e1c4eb9c9461510407d5e81'] }, { _id: '5dfa26f46719311869ac1756', name: 'Account 2', email: 'account2#gmail.com', spaces: ['5e1c4689429a8a0decf16f69', '5e1c4eb9c9461510407d5e81'] },{ _id: '5dfa270c6719311869ac1757', name: 'Account 3', email: 'account3#gmail.com', spaces: ['5e1c4689429a8a0decf16f69'] }],
result = spaces.map(({ _id, challengers }) => ({
_id,
challengers: challengers.map(
Map.prototype.get,
new Map(users.map(o => [o._id, o]))
)
}));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
I have an array in my state :
projects: [
{ title: 'todo 1', person: 'Sam', status: 'ongoing'},
{ title: 'project', person: 'Jack', status: 'complete' },
{ title: 'Design video', person: 'Tim', status: 'complete' },
{ title: 'Create a forum', person: 'Jade', status: 'overdue' },
{ title: 'application', person: 'Jade', status: 'ongoing'},],
From this array (projects), I would like to generate a new array with Javascript and to get this result :
totalByPersonAndStatus : [
{person : 'Sam', complete: 0, ongoing: 1, overdue: 0 },
{person : 'Jack', complete: 1, ongoing: 0, overdue: 0 },
{person : 'Tim', complete: 1, ongoing: 0, overdue: 0 },
{person : 'Jade', complete: 0, ongoing: 1, overdue: 1 },]
I tried it
totalProjectsByPersonAndStatus: state => {
state.projects.forEach(name => {
state. totalByPersonAndStatus["name"] = name.person;
});
return state. totalByPersonAndStatus;
The problem, if a make a console.log(this.totalByPersonAndStatus) I have an object with only the data of projects.name [name: "Jade", __ob__: Observer]
Can you help me ?
Thank you
You can use reduce
let projects =[{title:'todo1',person:'Sam',status:'ongoing'},{title:'project',person:'Jack',status:'complete'},{title:'Designvideo',person:'Tim',status:'complete'},{title:'Createaforum',person:'Jade',status:'overdue'},{title:'application',person:'Jade',status:'ongoing'},]
let desired = projects.reduce((output,{person,status}) => {
if( output[person] ){
output[person][status]++
} else {
output[person] = {
person,
complete: Number(status==='complete'),
ongoing: Number(status==='ongoing'),
overdue: Number(status==='overdue')
}
}
return output;
},{})
console.log(Object.values(desired))
Create a new Set for people and statuses by iterating through the projects, a set has only unique values so sets are a convenience, iterate through your people set creating a new object with all the statuses initialized to 0, then iterate over the projects to increment the various statuses that apply. This method allows any number of new statuses to be added without changing the code - dynamic.
var people = new Set();
var status = new Set();
projects.forEach((p)=>{
people.add(p.person);
status.add(p.status);
});
var totalByPersonAndStatus = [];
people.forEach((person)=>{
let peeps = { "person": person };
status.forEach((stat)=>{
peeps[stat] = 0;
});
projects.forEach((project)=>{
if (project.person === person) { peeps[project.status]++; }
});
totalByPersonAndStatus.push(peeps);
});
You could use reduce and destructuring like this:
const projects=[{title:'todo 1',person:'Sam',status:'ongoing'},{title:'project',person:'Jack',status:'complete'},{title:'Design video',person:'Tim',status:'complete'},{title:'Create a forum',person:'Jade',status:'overdue'},{title:'application',person:'Jade',status:'ongoing'}]
const merged = projects.reduce((acc,{person,status})=>{
acc[person] = acc[person] || { person, ongoing:0, complete:0, overdue:0}
acc[person][status]++;
return acc;
},{})
console.log(Object.values(merged))
The goal is create an object merged with each person as key and then increment based on the statuses:
{
"Sam": {
"person": "Sam",
"ongoing": 1,
"complete": 0,
"overdue": 0
},
"Jack": {
}
...
}
Then use Object.values, to get the final array.
You could make it a one-liner:
const projects=[{title:'todo 1',person:'Sam',status:'ongoing'},{title:'project',person:'Jack',status:'complete'},{title:'Design video',person:'Tim',status:'complete'},{title:'Create a forum',person:'Jade',status:'overdue'},{title:'application',person:'Jade',status:'ongoing'}],
output = Object.values(projects.reduce((a,{person,status})=>
((a[person] = a[person] || {person,ongoing:0,complete:0,overdue:0})[status]++,a),{}))
console.log(output)