I need to find nested objects in MongoDb using the Node.js Driver.
I'm having trouble accessing nested properties when the property name is dynamic. Here's my code:
//This gives expected results but "name1" isn't dynamic
collection.find({ 'followers.name1': { $exists: false } })
//Here's what I tried that does not give expected results
const username = "name1"
let query = { followers: {} }
query.followers[username] = { $exists: false }
collection.find(query)
Here's an example of the database structure:
{
"_id":"xxxxxxxxxxx",
"dateAdded":"2017-09-20T08:36:40.325Z",
"followers":{
"name1":{
"followedOn":"2017-09-20T08:36:40.325Z",
"unfollowedOn":null
},
"name2":{
"followedOn":"2017-09-20T08:36:40.325Z",
"unfollowedOn":null
}
}
}
Edit: my question is not a duplicate of the one marked as a duplicate. MongoDb find() argument is not an object literal. That's the whole point of my question, using like it like an object literal doesn't work.
I found the solution myself in the end. The key needs to be a string so you need to do:
const username = 'name1'
let query = {}
query['followers.'+username] = { $exists: false }
collection.find(query)
Related
I have a Node.js program that is using Mongo Atlas search indexes and is utilizing the Aggregate function inside of the MongoDB driver. In order to search, the user would pass the search queries inside of the query parameters of the URL. That being said, I am trying to build a search object based on if a query parameter exists or not. In order to build the search object I am currently using object spread syntax and parameter short-circuiting, like so:
const mustObj = {
...(query.term && {
text: {
query: query.term,
path: ['name', 'description', 'specs'],
fuzzy: {
maxEdits: 2.0,
},
},
})
}
This is a shortened version, as there are many more parameters, but you get the jest.
In a MongoDB search query, if you have multiple parameters that must meet a certain criteria, they have to be included inside of an array called must, like so:
{
$search: {
compound: {
must: [],
},
},
}
So, in order to include my search params I must first turn my mustObj into an array of objects using Object.keys and mapping them to an array, then assigning the searches 'must' array to the array I've created, like so:
const mustArr = Object.keys(mustObj).map((key) => {
return { [key === 'text2' ? 'text' : key]: mustObj[key] };
});
searchObj[0].$search.compound.must = mustArr;
What I would like to do is, instead of creating the mustObj and then looping over the entire thing to create an array, is to just create the array using the spread syntax and short-curcuiting method I used when creating the object.
I've tried the below code, but to no avail. I get the 'object is not iterable' error:
const mustArr = [
...(query.term && {
text: {
query: query.term,
path: ['name', 'description', 'specs'],
fuzzy: {
maxEdits: 2.0,
},
},
})
]
In all, my question is, is what I'm asking even possible? And if so, how?
Corrected based on #VLAZ comment:
while spread with array [...(item)], item has to be array (iterable).
When you use short-circuit, the item as below,
true && [] ==> will be `[]` ==> it will work
false && [] ==> will be `false` ==> wont work (because false is not array)
try some thing like (Similar to #Chau's suggestion)
const mustArr = [
...(query.term ? [{
text: {
query: query.term,
path: ['name', 'description', 'specs'],
fuzzy: {
maxEdits: 2.0,
},
},
}] : [])
]
I am currently using array filters to update the nested object.
My structure is -
Category Collection -
{
name:Disease,
_id:ObjectId,
subCategory:[{
name:Hair Problems,
_id:ObjectId,
subSubCategory:[{
name: Hair Fall,
_id:ObjectId
},{
name: Dandruff,
_id:ObjectId
}]
}]
}
I want to update the subsubcategory with id 1.1.1 which I am doing by using array filters.
let query = { 'subCategories.subSubCategories._id': subSubId };
let update = { $set: { 'subCategories.$.subSubCategories.$[j]': data } };
let option = { arrayFilters: [{ 'j._id': subSubId }], new: true };
await Categories.findOneAndUpdate(query, update, option
This code is working fine but array filters change the object id of subsubCategory. Is there any other alternative to do so without changing the ObjectId.
Thanks in advance
You can loop over the keys which you are getting as payload and put inside the $set operator.
const data = {
firstKey: "key",
secondKey: "key2",
thirdKey: "key3"
}
const object = {}
for (var key in data) {
object[`subCategories.$.subSubCategories.$[j].${key}`] = data[key]
}
let query = { 'subCategories.subSubCategories._id': subSubId };
let update = { '$set': object };
let option = { 'arrayFilters': [{ 'j._id': subSubId }], 'new': true };
await Categories.findOneAndUpdate(query, update, option)
Problem is in $set line there you have not mentioned specific fields to be update instead subCategory.$.subSubCategory.$[j] will replace complete object element that matches the _id filter. Hence your _id field is also getting updated. You have to explicitly mention the field name after array element identifier. See example below:
Suppose you want to update name field in subSubCategories from Dandruff to new Dandruff. Then do this way:
let update = { $set: { 'subCategories.$.subSubCategories.$[j].name': "new Dandruff" } };
This will only update name field in subSubCategories array
I need to query for 2 dynamic properties using the node.js driver for mongodb.
Here's the data structure:
{
"_id":"123456",
"dateAdded":"2017-09-20T08:36:40.325Z",
"followers":{
"name1":{
"followedOn":"2017-09-20T08:36:40.325Z",
"unfollowedOn":null
},
"name2":{
"followedOn":"2017-09-20T08:36:40.325Z",
"unfollowedOn":null
}
}
}
Here's my code:
//Working but not dynamic
collections.find({ '_id': '123456', 'followers.name1': { $exists: false } })
//My failed attempt at making it dynamic
const id = "123456"
const username = "name1"
let query = {}
query['followers.'+username] = { $exists: true }
collections.find( { "_id": id, query }
Note that this is not a duplicate of "how to make a dynamic key in an object literal". The .find() method of node.js mongodb driver does not accept object literals. I can't find documentation of what it accepts exactly.
Your _id property needs to be within query object, not separate.
Here's how to do it:
let query = { _id: id };
query['followers.'+username] = { $exists: true }
collections.find(query);
{
_id:xxxxstoreid
store:{
products:[
{
_id:xxxproductid,
name:xxx,
img:url,
}
]
}
}
since i cannot predict the request for update, params may have just name or it may have both.
here is my query,it updates successfully but it removes other fields if they are not present in the params.
eg:
var params={_id:xxid,name:'xxx',img:'xxx'}
or
var params={_id:xxid,name:'xxx'}
in this case if params have just name it removes img field and updates.
User.update({'store.products._id':params._id},{$set:{"store.products":params}},callback);
You need to supply the multiple keys to $set with the positional $ operator to update both matched keys.
I prefer the modern ES6 way of object manipulation:
let params = { "_id" : "xxxproductid", "name" : "xxx", "img" : "yyy" };
let update = [
{ 'store.products._id': params._id },
{ "$set": Object.keys(params).filter(k => k != '_id')
.reduce((acc,curr) =>
Object.assign(acc,{ [`store.products.$.${curr}`]: params[curr] }),
{ })
}
];
User.update(...update,callback);
Which would produce the call to MongoDB as ( with mongoose.set('debug', true) ) turned on so we see the request:
Mongoose: users.update({ 'store.products._id': 'xxxproductid' }, { '$set': { 'store.products.$.name': 'xxx', 'store.products.$.img': 'yyy' } }, {})
Where basically you take your input params and supply the _id as the first argument for the "query" :
{ 'store.products._id': params._id },
The rest takes the "keys" from the object via Object.keys which makes an "array" which we can "filter" with Array.filter() and then pass to Array.reduce to transform those keys into an Object.
Inside the .reduce() we call Object.assign() which "merges" objects with the given keys, generated in this form:
Object.assign(acc,{ [`store.products.$.${curr}`]: params[curr] }),
Using the template syntax to assign the "current" (curr) "key" into the new key name, again using the ES6 key assignment syntax []: which allows variable names in object literals.
The resulting "merged" object is passed back to be assigned to the "root" object where $set is used for the key of the update, so the "generated" keys are now children of that.
I use an array for the arguments purely for debugging purposes, but then that also allows cleaner syntax on the actual .update() using the "spread" ... operator to assign the arguments:
User.update(...update,callback);
Clean and simple, and some JavaScript techniques that you should learn for object and array manipulation. Mostly since the MongoDB query DSL is basically "Objects" and "Arrays". So learn to manipulate them.
function updateProducts(params) {
var query = {'store.products': {$elemMatch: {_id: params._id}}}
var updateObject = null;
if (params.name && params.img) {
updateObject = {$set: {'store.products.$': params}}
} else if(params.name && !params.img) {
updateObject = {$set: {'store.products.$.name': params.name}}
} else if (params.img && !params.name) {
updateObject = {$set: {'store.products.$.img': params.img}}
}
User.update(query, updateObject, callback)
}
The below query will use $ positional operator to locate the array element at index found from matching the array by _id in the query document followed by updating the fields for the element with the params values.
var params = {_id:1, name:'xxx',img:'yyy'};
var id = params['_id']; // Get id to locate matching array index
delete params['_id']; // Remove id from the object
Object.keys(params).forEach(function (key){params['store.products.$.'+key] = params[key]; delete params[key];}) // Transforms remaining object keys to include the positional $ placeholder to { "store.products.$.name" : "xxx", "store.products.$.img" : "yyy" }
User.update('store.products._id':id},{$set:params},callback);
quick question about Firebase lists.
I'm trying to access multiple lists to iterate through that are in a single object. The object looks like this:
user : {
playlists: {}, // List 1
joined: {}, // List 2
favorites: {} // List 3
}
Now I know I can access all three lists by doing three separate requests like:
firebase.list('user/playlists');
firebase.list('user/joined');
firebase.list('user/favorites');
But I'm trying to clean up my code and do it all in one by using:
firebase.list('user');
Then accessing all the lists it returns inside of the user. The only problem is, the lists aren't returned with keys, it just says "object" instead. When I use firebase.object('user') the keys are available.
This is what it looks like when I request 'user' as a list:
As you can see the key is inside the object and not outside of it ($key). How do I go about accessing these objects like I would if the key was actually the key (eg. user.playlists).
For anyone interested I figured it out. I used the .map() to convert parts of the object into arrays.
getPlaylist(_playlistId: string, _query: Object = {}) {
return this._database.object(`playlists/${_playlistId}`, _query).map(
result => {
return {
info: result.info,
leader: result.leader,
favorites: (result.favorites) ? Object.values(result.favorites) : [],
followers: (result.followers) ? Object.values(result.followers) : [],
songs: (result.songs) ? Object.values(result.songs) : []
};
}
);
}
For example, favorites is an object that I want to be returned as a list:
Check if exists If exists, map obj to array else assign to empty array
(result.favorites) ? Object.values(result.favorites) : []