How to merge JSON objects based on key/value pair? - javascript

I need to combine results from two different documents in Mongo. I have a function like this:
async function getReviewsByUserId (req, res) {
const { userId } = req.params
const reviews = await Review.find({ userId }).lean() || []
return res.status(200).send(reviews.reverse())
}
The reviews array looks like this:
{
"_id" : ObjectId("1263b55ef2cdd3ebb0654d1dd"),
"launchId" : "7fb83b40-7c6f-4099-aaed-fe9d0dc03111",
"userId" : "1",
}
{
"_id" : ObjectId("6355565cf5ef2cddebb065584"),
"launchId" : "12b53940-136f-3399-aaed-fe9d0dc05473",
"userId" : "7fb83b40-7c6f-4099-aaed-fe9d0dc03112",
}
I need to use the launchId from each review, look up a launch object from my mongo database, and combine that with the correct object in the reviews array.
Example of what I mean:
async function getReviewsByUserId (req, res) {
const { userId } = req.params
const reviews = await Review.find({ userId }).lean() || []
const launches = await Launch.find(/* find all launches where launch._id is equal to reviews.launchId*/)
return res.status(200).send(launches.reverse())
}
So if launches data looks like this (and launches is also an array of ALL launches):
{
"_id" : "12b53940-136f-3399-aaed-fe9d0dc05473",
"name" : "The Park",
}
Then how can I merge this with the reviews payload where the launch._id == reviews.launchId so that the final data looks like this:
{
"_id" : ObjectId("1263b55ef2cdd3ebb0654d1dd"),
"launchId" : "7fb83b40-7c6f-4099-aaed-fe9d0dc03111",
"userId" : "1",
}
{
"_id" : ObjectId("6355565cf5ef2cddebb065584"),
"launchId" : "12b53940-136f-3399-aaed-fe9d0dc05473",
"userId" : "7fb83b40-7c6f-4099-aaed-fe9d0dc03112",
"launch": {
"_id" : "12b53940-136f-3399-aaed-fe9d0dc05473",
"name" : "The Park",
}
}

This could be achieved by using aggregate pipelines.
Filter reviews by userId in $match stage
$lookup for launches
$unwind the launch array to an object
The solution could be:
async function getReviewsByUserId(req, res) {
const { userId } = req.params;
const launches = await Review.aggregate([
{
$match: {
userId
}
},
{
$lookup: {
from: "launches",
localField: "launchId",
foreignField: "_id",
as: "launch"
}
},
{
$unwind: {
path: "$launch",
preserveNullAndEmptyArrays: true
}
}
]);
return res.status(200).send(launches.reverse());
}

You can map() your reviews, and for each item you can use find() on the launches array to check if the launch id matches the current review id.
If there is no match, just return the review unaltered; if you get a match, you add the launches property to the current review and return it.
function ObjectId(oid) {
return oid
}
const reviews = [{
"_id": ObjectId("1263b55ef2cdd3ebb0654d1dd"),
"launchId": "7fb83b40-7c6f-4099-aaed-fe9d0dc03111",
"userId": "1",
},
{
"_id": ObjectId("6355565cf5ef2cddebb065584"),
"launchId": "12b53940-136f-3399-aaed-fe9d0dc05473",
"userId": "7fb83b40-7c6f-4099-aaed-fe9d0dc03112",
}
]
const launches = [{
"_id": "12b53940-136f-3399-aaed-fe9d0dc05473",
"name": "The Park",
}]
const res = reviews.map(r => {
const found = launches.find(l => l._id === r.launchId)
if (found) {
r.launch = {
_id: found._id,
name: found.name
}
return r
} else {
return r
}
})
console.log(res)

Related

How to loop through objects and count unique values of key?

I have logs of json files which are objects that look like
{
"logs": [
{
"id": "12321321321321",
"email": "test#email.com",
"message": "ahahaha"
},
{
"id": "12321321312",
"email": "test#email.com",
"message": "hahahaha."
},
"id": "12321321321"
}
I need to return a new object that contains
{
"hello_id": outer id of the json file,
"array": [
{
"email": "test#me.com",
"total": 2
}
]
}
So far I am looping through the json files and have
jsonsInDirectory.forEach((file) => {
const fileData = fs.readFileSync(path.join("./logs", file), "utf8");
const jsonData = JSON.parse(fileData);
}
});
The key is "logs" and "id" and the values are the objects in the "logs" and the value of "id"
How can I count and return a new object at the same time?
You can try this approach: make a hash object that counts emails. Then just map it to an array of objects.
const data = {
logs: [{
id: "89004ef9-e825-4547-a83a-c9e9429e8f95",
email: "noah.sanchez#me.com",
message: "successfully handled skipped operation."
},
{
id: "89004ef9-e825-4547-a83a-c9e9429e8f95",
email: "noah.sanchez#me.com",
message: "successfully handled skipped operation."
},
{
id: "89004ef9-e825-4547-a83a-c9e9429e8f95",
email: "noname#me.com",
message: "successfully handled skipped operation."
}],
id: "56f83bed-3705-4115-9067-73930cbecbc0",
};
const emails = data.logs.reduce((acc, { email }) => {
acc[email] = (acc[email] ?? 0) + 1;
return acc;
}, {});
const tally = Object.entries(emails)
.map(([email, total]) => ({ email, total }));
const result = { logs_id: data.id, tally };
console.log(result)
.as-console-wrapper { max-height: 100% !important; top: 0 }
When you do const jsonData = JSON.parse(fileData);, you get the file data as a JSON and knowing the struct of that JSON you can easily get the info.
I have created a example https://codesandbox.io/s/stackoverflow-logs-count-example-jys2vg?file=/src/index.js
It may not solve exactly wath you want.
To solve this problem with the most time efficiency, you can create a tally object by firstly creating a map of the occurences of each mail with the email as the key and no of occurences as the value, since this will take constant (O(1)) time to execute, afterwhich you can create the tally array from the map as given below
output = []
jsonsInDirectory.forEach((file) => {
const fileData = fs.readFileSync(path.join("./logs", file), "utf8");
const jsonData = JSON.parse(fileData);
var map = {}
jsonData.logs.forEach((log) => {
if(log.email in map){
map[log.email] += 1
}
else {
map[log.email] = 1
}
});
var tally = []
for(var email in map){
tally.push({email: email, total: map[email]})
}
output.push({logs_id: jsonData['id'], tally: tally});
})

How to dynamically nest $or operator into $and Mongoose statement?

Suppose I have 3 MongDB entries in a movie database:
"books": [
{
"_id": "xxxxxx",
"title": "Fast Five",
"rating": 6
},
{
"_id": "yyyyyy",
"title": "Kill Bill",
"rating": 8
},
{
"_id": "zzzzzzzz",
"title": "Jumanji",
"rating": 5
}
]
I use GraphQL to retrieve the id of multiple movies if their title and rating match the criteria:
query{
getMovies(itemFilterInput: {titles:["Fast Five", "Jumanji"], rating:6}){
_id
title
}
}
This should return Fast Five.
I want this to translate into a mongoDB query like this:
{$and: [{rating:6}, {$or: [{title:"Fast Five", title:"Jumanji"}]}]}
In the backend, I use NodeJS and Mongoose to handle this query and I use something like this:
getMovies: async args => {
try{
let d = args.itemFilterInput;
let filters = new Array();
const keys = Object.keys(d);
keys.forEach((key) => {
// The titles case
if(d[key] instanceof Array){
var sObj = {};
var sObjArray = [];
d[key].map(prop => {
sObj[key] = prop;
sObjArray.push(sObj);
});
filters.push({$or: sObjArray});
}else{
// The rating case
var obj = {};
obj[key] = d[key];
filters.push(obj);
}
});
const items = await Item.find({$and: filters});
return items.map(item => {
return item;
});
} catch (err) {
throw err;
}
}
Unfortunately, this approach does not work.
What is the correct way to nest $or parameters in $and params?
Answer:
First of all, it is better to use the $in operator instead of a $and/$or combination.
And to solve the error: use quotes everywhere to declare the query.

Firebase cloud function won't return anything from forEach?

When my trigger receives an update, I am trying to loop through another node via the admin sdk to iterate through the results from the snapshot with matching keys from the wildcard {itemId}. For some reason my forEach seems to not do anything and I don't know why. I have been trying to resolve this for quite some time now. I have never had a problem querying my firebase DB but now that I am trying to do so in cloud functions I have not been having any luck at all. I don't know if this is related to firebase-admin at all?
Here is my code, I left some comments to show you what I am trying to accomplish more clearly:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
// admin.initializeApp();
//is this enough or do I need to add more configuration???
admin.initializeApp(functions.config().firebase);
exports.itemUpdate = functions.database
.ref('/items/{itemId}')
.onUpdate((change, context) => {
const before = change.before.val();
const after = change.after.val();
console.log(after); //data I want to use to update query results
//if (before === after) {
//console.log('data didnt change')
//return null;
//}
admin.database().ref('users')
.orderByChild('likedItems')
.equalTo(context.params.itemId)
.once('value')
.then(snapshot => {
//I am able to log the snapshot, but the data is misleading???
console.log(snapshot);
//I am trying to look for the likedItems that match the itemId (wild card)
//from my query - Why wont anything log? Is is a problem with my admin?
snapshot.forEach((childSnapshot) => {
var key = childSnapshot.key;
var targetItem = context.params.itemId; //item(s) to update
var child = childSnapshot.child;
var childData = childSnapshot.val();
//attempt to update childData that match the targetItem (that was originally updated)
//targetItem returns undefined here but when i log it its not undefined?
return childData.likedItems.targetItem.update(after);
});
return;
}).catch(error =>{
console.log(error);
});
});
Here is a look at what my DB structure looks like for the items, and then the users likedItems in their node:
"items" : {
"Item1" : {
"title" : "title1",
"type" : "type1"
},
"Item2" : {
"title" : "title2",
"type" : "type2"
},
"Item3" : {
"title" : "title3",
"type" : "type3"
},
"Item4" : {
"title" : "title4",
"type" : "type4"
},
...
...
...
},
"users" : {
"EoAhYX3mYcRASr5oPD1eSZ979Vr1" : {
"followerCount" : 4,
"followingCount" : 2,
"likedItems" : {
"Item1" : {
"title" : "title1",
"type" : "type1"
},
"Item10" : {
"title" : "title10",
"type" : "type10"
},
"Item12" : {
"title" : "title12",
"type" : "type12"
}
},
"username" : "user1"
},
...
...
...
}
I appreciate all the help/guidence I can get!
Cheers.

Find and Update Object in Mongoose [duplicate]

This question already has answers here:
How do you update objects in a document's array (nested updating)
(4 answers)
Closed 5 years ago.
This is my Schema:
const UserSchema = new mongoose.Schema({
name: String,
chats: [{
lastMessage: String,
lastUpdate: Date
}]
});
Users Collection:
{
"_id" : ObjectId("59987ef42aafc45204ee8bc3"),
"name" : "Nico",
"chats" : [
{
"_id" : ObjectId("599e58265cf2799c07925488")
"lastMessage": "Test",
"lastUpdate": "Test"
},
{
"_id" : ObjectId("599e59218d4a52c7071f46df")
"lastMessage": "Hi",
"lastUpdate": "01/01/2017"
}
]
},
{
"_id" : ObjectId("59987ef42aafc45204ee8bc3"),
"name" : "Lucas",
"chats" : [
{
"_id" : ObjectId("599e59218d4a52c7071f46df")
"lastMessage": "Hi",
"lastUpdate": "01/01/2017"
}
]
}
I am trying to implement chat on my app. I am working on my function that will find and update (on each User document, on Chat array with ObjectID as equals being received by request) but i don't know whats the best approach and how to implement it.
The request to my endpoint will give me ChatID, UserID, and MessageBody.
So my two approaches are:
1) Find by UserID, and then findAndUpdate on chat array prop where _idChat is equals as ChatID.
2) Find in Collection where _idChat is equals as ChatID and then update (this should retrieve me Users that have any object with ChatID and then update it)
This is what i am trying to implement (but having nothing being updated):
static sendMessage(req: express.Request, res: express.Response) {
const message = req.body.message;
const idChat = req.body.idChat;
const userIds = [req.body.sender, req.body.receiver];
const updatePromises = [];
userIds.forEach( userId => {
updatePromises.push(
UserDao['updateChat']
(
userId, idChat,
{'$set': {'chats.$.lastMessage': message, 'chats.$.lastUpdate': new Date() }},
);
});
Promise.all(updatePromises)
.then(values => {
return res.status(200).json(values);
}).catch(reason => {
return res.status(400).json(reason);
});
userSchema.static('updateChat', (userId, chatId, query) => {
return new Promise((resolve, reject) => {
if (!_.isObject(query)) {
return reject(new TypeError('query is not a valid Object.'));
}
if (!_.isString(userId)) {
return reject(new TypeError('userId is not a valid String.'));
}
User
.update(
{_id: userId, 'chats._id': chatId},
{$set: query},
{upsert: true}
).exec((err, updated) => {
err ? reject(err) : resolve(updated);
});
});
});
Thanks for your help!
How about this?
User.update({
_id: request.userId,
chats: {
$elemMatch: {
_id: request.chatId
}
}
}, {
$set: {
'chats.$.lastMessage': request.messageBody,
'chats.$.lastUpdate': new Date()
},
},
function(err) {
// handle error
console.log(err);
}
)
You are going to update the exact chat that matches your criteria because you'll be using the $ operator on the $set command.

Why this firebase calling always returns null?

Consider the following firebase structure:
{
"users": {
"h7iStd4QvAYWh9xPbsiFuByDd1j1" : {
"email" : "jobs#apple.com",
"name" : "Steve Jobs",
"rank" : 0
},
"wkA0DrJs9wdBlfAkJuKIrnhNzyq2" : {
"email" : "gates#microsoft.com",
"name" : "Bill Gates",
"rank" : 0
}
}
}
and this data retrieval:
const email = 'jobs#apple.com';
firebase.database().ref('users').equalTo(email, 'email').once('value')
.then((snapshot) => {
console.log(email, snapshot.key, snapshot.val());
// snapshot.val() always returns null
})
.catch((error) => {
console.error(error);
});
Why snapshot.val() returns null? What is the correct approach to fetch the whole object concerning Steve Jobs data, the child with h7iStd4QvAYWh9xPbsiFuByDd1j1 key?
Try this instead:
firebase.database().ref('users').orderByChild('email').equalTo(email).once('value')

Categories