Merge an array of objects using key value in lodash? - node.js

I'm using node.js and lodash.
I have data like this:
[
{
to: [ 'foo#bar.com', 'foo1#bar.com' ],
submittedSubs: [ [Object] ]
},
{
to: [ 'foo#bar.com', 'foo2#bar.com' ],
submittedSubs: [ [Object], [Object], [Object] ]
}
]
I'd like to turn it into data like this where it's "sorted" by to
[
{
to: 'foo#bar.com',
submittedSubs: [ [Object],[Object], [Object], [Object] ]
},
{
to: 'foo1#bar.com',
submittedSubs: [ [Object] ]
},
{
to: 'foo2#bar.com',
submittedSubs: [ [Object], [Object], [Object] ]
}
]
How can I do this?
I've tried this:
spam[0].to.push('foo#bar.com');
spam[0].to.push('foo1#bar.com');
spam[1].to.push('foo#bar.com');
spam[1].to.push('foo2#bar.com');
console.log('data is',spam);
var byUser=[];
_.each(spam, function(data){
_.each(data.to,function(addr){
byUser.push({to:addr,submittedSubs:data.submittedSubs});
});
});
console.log('attempt',_.merge(byUser));
But that gives me this:
[ { to: 'foo#bar.com', submittedSubs: [ [Object] ] },
{ to: 'foo1#bar.com', submittedSubs: [ [Object] ] },
{ to: 'foo#bar.com', submittedSubs: [ [Object], [Object], [Object] ] },
{ to: 'foo2#bar.com', submittedSubs: [ [Object], [Object], [Object] ] } ]

This'll work for you:
var unique = {};
byUser.forEach(function(user) {
unique[user.to] = unique[user.to] || [];
unique[user.to] = unique[user.to].concat(user.submittedSubs);
});
unique = Object.keys(unique).map(function (key, i) {
return {to: key, submittedSubs: unique[key]};
});
/*
[ { to: 'foo#bar.com', submittedSubs: [ [Object] ] },
{ to: 'foo1#bar.com', submittedSubs: [ [Object] ] },
{ to: 'foo2#bar.com', submittedSubs: [ [Object], [Object], [Object], [Object] ] } ]
*/
I stand by that this should be achievable using the callback feature of _.uniq but I couldn't get it to work the way you needed it to.
You should be able to use _.uniq from lodash on your final array:
_.uniq(byUser, "to");
/*
[ { to: 'foo#bar.com', submittedSubs: [ [Object] ] },
{ to: 'foo1#bar.com', submittedSubs: [ [Object] ] },
{ to: 'foo2#bar.com', submittedSubs: [ [Object], [Object], [Object] ] } ]
*/

I imagine there's some nice lodash facilities to shorten this up a bit, but here's a vanilla-js solution:
var data = [
{
to: [ 'foo#bar.com', 'foo1#bar.com' ],
submittedSubs: [{ id: 'sub1' }]
},
{
to: [ 'foo#bar.com', 'foo2#bar.com' ],
submittedSubs: [{ id: 'sub2' }, { id: 'sub3' }, { id: 'sub4' }]
}
];
var emailSubsMap = data.reduce(function(result, record) {
record.to.forEach(function(email) {
result[email] = (result[email] || [])
.concat(record.submittedSubs);
});
return result;
}, {});
var formatted = Object.keys(emailSubsMap).map(function(email) {
return { to: email, submittedSubs: emailSubsMap[email] };
}).sort(function(a, b) {
return a.to <= b.to ? -1 : 1;
});
console.log(JSON.stringify(formatted));
(Formatted) console output:
[
{
"to": "foo1#bar.com",
"submittedSubs": [
{ "id": "sub1" }
]
},
{
"to": "foo2#bar.com",
"submittedSubs": [
{ "id": "sub2" },
{ "id": "sub3" },
{ "id": "sub4" }
]
},
{
"to": "foo#bar.com",
"submittedSubs": [
{ "id": "sub1" },
{ "id": "sub2" },
{ "id": "sub3" },
{ "id": "sub4" }
]
}
]
Note that I mocked up what the submittedSubs objects might look like, simply for testing purposes.
JSFiddle Example
A couple notes about the sorting:
My first version wasn't sorting correctly... it's been updated. :)
The sort method you're requesting doesn't follow JavaScript's "native" string sort order. E.g., ['foo#bar.com', 'foo2#bar.com', 'foo1#bar.com'].sort() --> ['foo1#bar.com','foo2#bar.com','foo#bar.com'], so if you really want foo#bar.com to come before foo1#bar.com, you'll need to define your sort criteria in a little more detail.

Related

Pagination with aggregate search in mongoose

const page = 1;
const limit = 10;
const searchResultsArray = await mongoose.connection.db
.collection('game_activity')
.aggregate([
{
$match: {
game_id
}
},
{
$addFields: {
activities: {
$filter: {
input: '$activity',
as: 'activity',
cond: {
$and: condition
}
}
}
}
},
{
$project: {
activities: 1,
activity_number: { <--- can't read property error
$size: '$activities'
}
}
},
{ $limit: 50 },
{
$facet: {
paginatedResult: [ { $sort: searchFilter }, { $skip: (page - 1) * limit } ]
}
}
])
.toArray();
const searchResultsObject = searchResultsArray[0];
if (
searchResultsObject.paginatedResult[0].activity_number === undefined ||
searchResultsObject.paginatedResult[0].activity_number == 0
) {
const result = {
no_activities: true
};
res.json(result);
} else {
const result = {
search_result: {
total_activities: searchResultsObject.paginatedResult[0].activity_number,
current_page: page,
last_page: Math.ceil(searchResultsObject.paginatedResult[0].activity_number / 10),
searched_activities: searchResultsObject.paginatedResult[0].activities
},
no_activities: false
};
res.json(result);
}
I have this .aggregate() search function and trying to apply the pagination result. This format worked on other search but on this aggregate() search, I have 2 problems.
console.log(searchResultsObject.paginatedResult[0]);
-------- result --------
{
search_result: {
total_activities: 16,
current_page: 1,
last_page: 2,
searched_activities: [
[Object], [Object],
[Object], [Object],
[Object], [Object],
[Object], [Object],
[Object], [Object],
[Object], [Object],
[Object], [Object],
[Object], [Object]
]
},
no_activities: false
}
const limit = 10 does not applied here. It returns all of the activities.
when I set the const page = 2;, I get TypeError: Cannot read property 'activity_number' of undefined this error and console.log(searchResultsObject.paginatedResult[0]); shows `undefined
I want to return 10 gaming activities per page, so in this case, if I set const page =2;, then it will return the rest 6 activities. I couldn't quite figure out how to fix this since this works on other .aggregate() search that I have.
------------ MongoDB Document ---------
{
"_id": {
"$oid": "601942d93aca6ee8cb300327"
},
"location_id": "ddff23323443",
"activity": [{
"activity_id": "VVINxmhRHsnMwvfT",
"created_by": {
"id": "aa332",
},
"activity_type": "league_of_legends",
"activity_info": {
"location_id": "ddff23323443",
"activity_time": "2021-02-02 05:45:00.000Z",
"game_code": "6"
},
"attendee": ["aa332"]
}, {
"activity_id": "EBZNKmsFKDgdeDz0",
"created_by": {
"id": "aa332",
},
"activity_type": "league_of_legends",
"activity_info": {
"id": "ddff23323443",
"activity_time": "2021-02-02 05:45:00.000Z",
"game_code": "6"
},
"attendee": ["aa332"]
}, {
"activity_id": "j8J1Jlk8MtWPi1HT",
"created_by": {
"id": "aa332",
},
"activity_type": "league_of_legends",
"activity_info": {
"location_id": "bvvsd33",
"activity_time": "2021-02-02 05:45:00.000Z",
"game_code": "6"
},
"attendee": ["aa332"]
}]
}
----- expectation -----
const page = 1
{
search_result: {
total_activities: 16,
current_page: 1,
last_page: 2,
searched_activities: [
[Object], [Object],
[Object], [Object],
[Object], [Object],
[Object], [Object],
[Object], [Object],
]
},
no_activities: false
}
const page = 2
{
search_result: {
total_activities: 16,
current_page: 2,
last_page: 2,
searched_activities: [
[Object], [Object],
[Object], [Object],
[Object], [Object],
]
},
no_activities: false
}
Your expectation is not clear as per your try, i can explain the basic pagination process,
pagination configs
const page = 1;
const limit = 10;
// If you are starting page number from 1 then you need to minus 1
const skip = (page - 1) * limit;
$unwind deconstruct activities array
$replaceRoot replace activities object to root
$sort documents
$facet to separate result, result data and count, $skip should before the $limit
const searchResultsArray = await mongoose.connection.db.collection('game_activity').aggregate([
{ $match: { _id: game_id } },
{
$project: {
activities: {
$filter: {
input: '$activity',
as: 'activity',
cond: { $and: condition }
}
}
}
},
{ $unwind: "$activities" },
{ $replaceRoot: { newRoot: "$activities" } },
{ $sort: searchFilter },
{
$facet: {
searched_activities: [
{ $skip: skip },
{ $limit: limit }
],
total_activities: [
{ $count: "count" }
]
}
}
]).toArray();
No data validation response
// No Data Fond!
if (!searchResultsArray[0].total.length) {
res.json({ no_activities: true });
return;
}
Success response
res.json({
search_result: {
total_activities: searchResultsArray[0].total_activities[0].count,
current_page: page,
last_page: Math.ceil(searchResultsArray[0].total_activities[0].count / limit),
searched_activities: searchResultsArray[0].searched_activities
},
no_activities: false
});
Combine above code together in sequence and try.

Project from a double nested array by checking a value inside the array Mongodb

{
_id: 'uniquewId',
programs: [
{
prgName: 'prgOne',
progress: '5',
addedBy: 'coach'
exercises: [
{
date: '1/12/20',
exercises: [
{
exId: 'pushup',
progress: 5,
attributes: [
{
id: 'myId',
done: true
},
{
id: 'myId2',
done: false
}
]
},
{
exId: 'situp',
progress: 5,
attributes: [
{
id: 'myId',
done: true
},
{
id: 'myId2',
done: true
}
]
}
]
},
{
date: '2/12/20',
exercises: [
{
exId: 'pushup',
progress: 5,
attributes: [
{
id: 'myId',
done: true
},
{
id: 'myId2',
done: false
}
]
},
{
exId: 'situp',
progress: 5,
attributes: [
{
id: 'myId',
done: fase
},
{
id: 'myId2',
done: false
}
]
}
]
}
]
},
{
prgName: 'prgTwo',
progress: '5',
addedBy: 'coach2'
exercises: [
{
date: '1/12/20',
exercises: [
{
exId: 'pushup',
progress: 5,
attributes: [
{
id: 'myId',
done: true
},
{
id: 'myId2',
done: true
}
]
},
{
exId: 'situp',
progress: 5,
attributes: [
{
id: 'myId',
done: false
},
{
id: 'myId2',
done: false
}
]
}
]
},
{
date: '2/12/20',
exercises: [
{
exId: 'pushup',
progress: 5,
attributes: [
{
id: 'myId',
done: true
},
{
id: 'myId2',
done: false
}
]
},
{
exId: 'situp',
progress: 5,
attributes: [
{
id: 'myId',
done: true
},
{
id: 'myId2',
done: false
}
]
}
]
}
]
}
]
}
That above is the structure of my document.
What i want to do is from the programs array i want to project the name of exercises that have atleat one document inside attribute array which has done key marked as true. What i am trying to do is figure out exercises which has done set to true inside the attributes array
Is there any way i can get the output like:
{
_id: 'uniquewId',
programs: [
{
prgName: 'prgOne',
exercises: [
'pushup',
'situp'
]
},
{
prgName: 'prgTwo',
exercises: [
'pushup',
'situp'
]
}
]
}
It's a tedious process.
With $map operator we can iterate over arrays. Also $map allows transform array of objects into array of strings.
[{k1:v1}, {k2:v2}, ...] -> [v1, v2, ...]
Pseudocode
1st step. We perform complex aggregation and pre-calculate values
for (Programs program: doc.programs) {
return {
prgName : program.prgName,
exercises : [
for (Exercises exer1: program.exercises) {
for (Exercises exer2: exer1.exercises) {
return {
exId : exer2.exId,
done : exer2.attributes.done.contains(true)
}
}
}
]
}
}
2nd step. We construct desired result
db.collection.aggregate([
{
$project: {
programs: {
$map: {
input: "$programs",
as: "program",
in: {
prgName: "$$program.prgName",
exercises: {
$reduce: {
input: {
$map: {
input: "$$program.exercises",
as: "exer1",
in: {
$map: {
input: "$$exer1.exercises",
as: "exer2",
in: {
exId: "$$exer2.exId",
done: {
$in: [
true,
"$$exer2.attributes.done"
]
}
}
}
}
}
},
initialValue: [],
in: {
$concatArrays: [
"$$value",
"$$this"
]
}
}
}
}
}
}
}
},
{
$project: {
programs: {
$map: {
input: "$programs",
as: "program",
in: {
prgName: "$$program.prgName",
exercises: {
$reduce: {
input: {
$filter: {
input: "$$program.exercises",
cond: {
$eq: [
"$$this.done",
true
]
}
}
},
initialValue: [],
in: {
$concatArrays: [
"$$value",
{
$cond: [
{
$in: [
"$$this.exId",
"$$value"
]
},
[],
[
"$$this.exId"
]
]
}
]
}
}
}
}
}
}
}
}
])
MongoPlayground

How to convert Objects in an array to array of items after mongoose aggregate

I ran the following Query in mongoose and it is successful query
const recmessages = await messages.aggregate([
{
$match: {
conversationId: {
$in: mapedId
}
}
},
{
$sort: {
time: -1
}
},
{
$group: {
_id: "$conversationId",
messages: {
$push: "$$ROOT"
}
}
},
{
$project: {
messages: {
$slice: [
"$messages",
5
]
}
}
}
])
The returned data however has the following form. If I use {$unwind:$messages}, the returned messages array is no longer an object but the messages is no longer an array.
[ { _id: 5e7bb66d99aa010ca9bfeed9,
messages: [ [Object], [Object], [Object], [Object] ] },
{ _id: 5e7ba776c55fc40b216b0d12,
messages: [ [Object], [Object], [Object], [Object] ] } ]
my question how can I end up with the same query result, with the messages field a destructed array of items instead of objects

MongoDB: query multiple $elemmatch with $all

In my app, I have a polls collection and the a document looks like this;
/* POLLS */
{
...
"type": "COMMON",
"__v": {
"$numberInt": "0"
},
"targets": [
{
"code": "city",
"logic": "eq",
"value": "4"
},
{
"code": "city",
"logic": "eq",
"value": "15"
}
]
}
And the targeting key is an array contains of objects of targets.
Each target's code can be city or gender.
So it means, "gender" may not be found in a Poll document.
Depending on that when fetching Polls, I want the ones matching with my User's fields, if the code exists.
For example; When fetching the Polls from a User's feed, If a Poll contains gender targeting, I want to find polls matching with my User's gender.
My code is like this:
Poll.find({
state: "PUBLISHED",
approval: "APPROVED",
$or: [
{
targets: {
$exists: true,
$eq: []
}
},
{
targets: {
$exists: true,
$ne: [],
$all: [
{
$elemMatch: {
code: {
$exists: true,
$eq: "city"
},
value: {
$eq: foundUser.cityCode // equals to 4 in this example
}
}
},
{
$elemMatch: {
code: {
$exists: true,
$eq: "gender"
},
value: {
$eq: foundUser.gender // equals to m in this example
}
}
}
]
}
}
]
})
However, the list returns empty for the Poll document I wrote above.
When the part below commented out, the code returns the correct polls.
Poll.find({
state: "PUBLISHED",
approval: "APPROVED",
$or: [
{
targets: {
$exists: true,
$eq: []
}
},
{
targets: {
$exists: true,
$ne: [],
$all: [
{
$elemMatch: {
code: {
$exists: true,
$eq: "city"
},
value: {
$eq: foundUser.cityCode // equals to 4 in this example
}
}
},
// {
// $elemMatch: {
// code: {
// $exists: true,
// $eq: "gender"
// },
// value: {
// $eq: foundUser.gender // equals to m in this example
// }
// }
// }
]
}
}
]
})
May anyone help me with the situation that return the right polls when gender or country targeted?
Thanks, Onur.
Return targets field is an array whose elements match the $elemMatch criteria in array
Try that query
Poll.find({
state: "PUBLISHED",
approval: "APPROVED",
$or: [
{
targets: {
$exists: true,
$eq: []
}
},
{
targets: {
$exists: true,
$ne: [],
$all: [
{
$elemMatch: {
code: {
$exists: true,
$eq: "city"
},
value: {
$eq: "4" // equals to 4 in this example
}
}
}
]
}
},
{
targets: {
$exists: true,
$ne: [],
$all: [
{
$elemMatch: {
code: {
$exists: true,
$eq: "gender"
},
value: {
$eq: 'm' // equals to m in this example
}
}
}
]
}
}
]
})

Actions on Google - Location Permission

How do you request device location using webhook, and actions on google v2.
Previously, I used permission_request field, but that is now deprecated, and not sure where it fits into the response object at.
json response
{
speech: "",
displayText: "",
data: {
google: {
expectUserResponse: true,
noInputPrompts: [
{
textToSpeech: "Hello McFly!"
},
{
textToSpeech: "Good talk, Russ"
},
{
textToSpeech: "Alright, I'm just gonna go over here and hang out. Let me know if there is anything else I can do for you."
}
],
richResponse: {
items: [
{
simpleResponse: {
textToSpeech: "Testing",
ssml: "<speak >Testing</speak>",
displayText: "Testing"
},
basicCard: null
}
],
suggestions: [ ],
linkOutSuggestion: {
destinationName: null,
url: null
}
},
systemIntent: null,
possibleIntents: [
{
intent: "actions.intent.PERMISSION",
inputValueData: {
#type: "type.googleapis.com/google.actions.v2.PermissionValueSpec",
optContext: "To provide weather information for you.",
permissions: [
"DEVICE_COARSE_LOCATION"
]
}
}
]
}
},
contextOut: [ ],
source: "Abbi"
}
so i needed to add the permission request as a system intent, not expected intent, or possible intent.
the below json works now
{
speech: "",
displayText: "",
data: {
google: {
expectUserResponse: true,
noInputPrompts: [
{
textToSpeech: "Hello McFly!"
},
{
textToSpeech: "Good talk, Russ"
},
{
textToSpeech: "Alright, I'm just gonna go over here and hang out. Let me know if there is anything else I can do for you."
}
],
richResponse: {
items: [
{
simpleResponse: {
textToSpeech: "",
ssml: "<speak ></speak>",
displayText: ""
},
basicCard: null
}
],
suggestions: [ ],
linkOutSuggestion: {
destinationName: null,
url: null
}
},
systemIntent: {
intent: "actions.intent.PERMISSION",
data: {
#type: "type.googleapis.com/google.actions.v2.PermissionValueSpec",
optContext: "To provide an accurate experience, ",
permissions: [
"DEVICE_PRECISE_LOCATION"
],
carouselSelect: null
}
}
}
},
contextOut: [ ],
source: "Abbi"
}

Resources