Mongoose - Search inside nested array with no limits - mongodb

My mongodb collection is stores records of groups which can have subgroups, which can further have sub-sub groups and there will not be any limits of nesting of this sub-groups. Is there anyway in mongoose to find a group name that resides in nth level?
My collection structure looks like the following, so suppose if i want to find if "My Group" has a subgroup named "My Group 1001", how am I supposed to find it?
{
"id": "60c18d4ce973fc139f23bf93",
"name": "My Group",
"subgroups": [
{
"id": "70c18d4ce973fc139f23bf93",
"name": "My Group 2",
"subgroups": [
{
"id": "80c18d4ce973fc139f23bf93",
"name": "My Group 3",
"subgroups": [
{
"id": "90c18d4ce973fc139f23bf93",
"name": "My Group 4",
"subgroups": [ ... ],
}
]
}
]
}
]
}
The other solutions that I found on stackoverflow suggested that it can be achieved using the dot notation for eg,
$or: [
{"name": "My Group 1001"},
{"subgroups.name": "My Group 1001"},
{"subgroups.subgroups.name": "My Group 1001"},
...
]
But since in my case the level is not known hence I cannot use the above pattern.

Mongodb is not an ideal tool for recursive operations.
Not recommended, but you can use $function to do the job with javascript:
db.collection.find({
$expr: {
$function: {
body: "function(subgroups, topName) { let recursiveSearch = function(sg){ if(!sg) {return false}; return sg.some(function(subdoc) {return subdoc.name == 'My Group 1001' || recursiveSearch(subdoc.subgroups)})}; return topName == 'My Group 1001' || recursiveSearch(subgroups);}",
args: [
"$subgroups",
"$name"
],
lang: "js"
}
}
})
The function alone formatted for readability:
function(subgroups, topName) {
let recursiveSearch = function (sg) {
if (!sg) { return false };
return sg.some(function (subdoc) {
return subdoc.name == 'My Group 1001' || recursiveSearch(subdoc.subgroups)
})
};
return topName == 'My Group 1001' || recursiveSearch(subgroups);
}
There is no tail recursion so I imagine it may blow up if it exhaust call stack. Didn't test it myself though.
If you have luxury of changing data format, consider adding a top level "names" flat array of all names from the nested documents. It will add some overhead to the application logic to keep it up to date on each update. The search will be as simple as
db.collection.find({"names": "My Group 1001"})
As an additional bonus you will be able to benefit from multikey indexes

Maybe finding keys with values can help you:
const flattenObj = (obj, parent, res = {}) => {
for (let key in obj) {
let propName = parent ? parent + '.' + key : key;
if (typeof obj[key] == 'object') {
flattenObj(obj[key], propName, res);
} else {
res[propName] = obj[key];
}
}
return res;
}
const obj = {
"id": "60c18d4ce973fc139f23bf93",
"name": "My Group",
"subgroups": [
{
"id": "70c18d4ce973fc139f23bf93",
"name": "My Group 2",
"subgroups": [
{
"id": "80c18d4ce973fc139f23bf93",
"name": "My Group 3",
"subgroups": [
{
"id": "90c18d4ce973fc139f23bf93",
"name": "My Group 4"
}
]
}
]
}
]
}
const dotNotation = flattenObj(obj);
// Finding keys from object by a specific value
const key1 = Object.keys(dotNotation).find(key => dotNotation[key] === 'My Group 2');
const key2 = Object.keys(dotNotation).find(key => dotNotation[key] === 'My Group 3');
const key3 = Object.keys(dotNotation).find(key => dotNotation[key] === 'My Group 4');
console.log(`Key of 'My Group 2': ` + key1);
console.log(`Key of 'My Group 3': ` + key2);
console.log(`Key of 'My Group 4': ` + key3);

Related

Flutter list query

Im trying to check a list inside list and then show it as ViewList
for example
const Sport = [
{
"Name": "James",
"SportTaq": ["Soccer", "American football"],
},
];
and then check list witch include loud Soccer
print(Sport.where((item) => item["SportTaq"].contains("Soccer")));
but it did not work can you help me with it please
You can do something like this:
void main() {
const Sport = [
{
"Name": "James",
"SportTaq": ["Soccer", "American football"],
},
{
"Name": "Bob",
"SportTaq": ["American football","basketball"],
},
];
print(Sport.where((item) {
List sports = item["SportTaq"];
return sports.contains("Soccer");
}).toList());
}

How json array pages by id dart

how all pages to map title print?
how json select example id=12 to map print title
{
"result": {
"name": "json1",
"pages": [
{
"zones": [
{
"title": "title1"
},
{
"title": "title2"
}
],
"id": 4
},
{
"zones": [
{
"title": "title3"
},
{
"title": "title4"
}
],
"id": 12
}
],
"creatorUserName": "admin",
"id": 2
}
}
List post = json;
children: post
.map( (post) => Container(
child: Center(child: Text(post.title]),)
))
.toList(),
I make a code to parse your json
var data = json.decode(jsonData);
var pagesArray = data["result"]["pages"];
pagesArray.forEach((page) {
var zones = page["zones"];
//Each id have your titles
int id = page["id"];
List<String> titles = new List();
zones.forEach((zone) {
titles.add(zone["title"]);
});
print("Id $id have this titles : ${titles.toString()}");
});

Insert Multiple records in dynamodb using api gateway

How can I insert multiple rows in dynamodb using body mapping template of API gateway?
Input to my code is "xyz 1,abc 2" which has information about 2 rows to be inserted.
Only second record which is "abc 2" is getting stored, I want both records to be inserted in the table. Below is the code I have written
#set($rawAPIData = $input.path('$'))
#set ($bulk = $rawAPIData.split(","))
{
"TableName": "tablename",
#foreach( $records in $bulk)
#set ($s = $records.split(" "))
"Item": {
"col1": {
"S": "$s.get(0)"
},
"col2": {
"S": "$s.get(1)"
}
}
#if( $foreach.hasNext ), #end
#end
}
I'm new to this, suggestion would really help
This AWS guide shows how to use API Gateway as a proxy for DynamoDB. It's similar the approach you are trying to take. As a suggestion, it might be better have your api focus on a single row at a time, rather than splitting multiple inputs on ,. For example it would simplify your template somewhat to send requests similar to those found in the guide.
Example Request Body:
{
"col1": "xyz",
"col2": "1"
}
Template (derived from your template code):
{
"TableName": "tablename",
"Item": {
"col1": {
"S": "$input.path('$.col1')"
},
"col2": {
"S": "$input.path('$.col2')"
}
}
}
However, if you want to stick to operating on multiple items, The BatchWriteItem documentation would be worth a read. Following the example, I think this should be your body template:
#set($rawAPIData = $input.path('$'))
#set ($bulk = $rawAPIData.split(","))
{
"RequestItems": {
"tablename": [
#foreach($records in $bulk)
#set ($s = $records.split(" "))
{
"PutRequest": {
"Item": {
"col1": {
"S": "$s.get(0)"
},
"col2": {
"S": "$s.get(1)"
}
}
}
}
#if( $foreach.hasNext ),
#end
]
}
#end
}
I used the similar approach as #Gerand, but I solved it using lambda. Here is the working code:
'use strict';
const AWS = require("aws-sdk");
const dynamodb = new AWS.DynamoDB();
exports.handler = (event, context, callback) => {
var data=event.data;
var bulk = data.split(",");
var toSave = [];
for(var i = 0; i < bulk.length; i++) {
var s=bulk[i].split(" ");
var item = {
"col1": {
S: s[0]
},
"col2": {
S: s[1]
}
};
toSave.push(item);
}
var items = [];
for(var i = 0; i < toSave.length; i++) {
items[i] = {
PutRequest: { Item: toSave[i] }
}
}
var params = {
RequestItems: {
'table_name': items
}
};
dynamodb.batchWriteItem(params, function(err, data) {
console.log("Response from DynamoDB");
if(err) console.log(err);
else console.log(data);
});
};

Finding multiple docs using same id not working, using meteor + react and mongoDB

How do I get the email address of the students in the same class_id, take it as there are more then 2 students in different class in the DB as well?
I have this but it return empty array []
Meteor.users.find({"course_learn_list.$.class_id": {$in: [classId]}},
{field: {"emails.address": 1}}
).fetch()
Collections
{
"_id": "LMZiLKs2MRhZiiwoS",
"course_learn_list": [
{
"course_id": "M8EiKfxAAzy25WmFH",
"class_id": "jePhNgEuXLM3ZCt98"
},
{
"course_id": "5hbwrfbfxAAzy2nrg",
"class_id": "dfbfnEuXLM3fngndn"
}
],
"emails": [
{
"address": "student1#gmail.com",
"verified": false
}
]
},
{
"_id": "JgfdLKs2MRhZJgfNgk",
"course_learn_list": [
{
"course_id": "M8EiKfxAAzy25WmFH",
"class_id": "jePhNgEuXLM3ZCt98"
},
{
"course_id": "5hbwrfbfxAAzy2nrg",
"class_id": "dfbfnEuXLM3fngndn"
}
],
"emails": [
{
"address": "student2#gmail.com",
"verified": false
}
]
}
I think you want:
Meteor.users.find({ "course_learn_list.class_id": classId },
{ "course_learn_list.$": 1, "emails.address": 1 }).fetch()
This should find the first instance in each course_learn_list array where the classId is your classId.
In this case you probably don't need to use a projection to get the right answer. Here's an example of extracting the verified email addresses using only the . operator in the selector:
const ids = ['jePhNgEuXLM3ZCt98', 'some-other-id'];
const emails =
Meteor.users.find({ 'course_learn_list.class_id': { $in: ids } })
.fetch()
.map(user => _.findWhere(user.emails, { verified: true }).address);
This works for me!
Meteor.publish("getMyClassStudents", function(classId) {
console.log("Publish getMyClassStudents")
var self = this
if (self.userId) {
var data = Meteor.users.find({
"course_learn_list.class_id": classId
}, {
"fields": {
"emails.address": 1
}
})
return data
}
else {
return self.ready()
}
})

Rearrrange populated json result in mongoose

A simple json response for Post.find().populate("name") will return json result as follow. Note: The focus of the question is to rearrange the "name":"Alex" in json to the final structure as shown. Ignore the part that need hiding _id and __v. Thanks.
[
{
"_id": "54cd6669d3e0fb1b302e54e6",
"title": "Hello World",
"postedBy": {
"_id": "54cd6669d3e0fb1b302e54e4",
"name": "Alex",
"__v": 0
},
"__v": 0
},
...
]
How could i rearrange and display the entire json as follow?
[
{
"_id": "54cd6669d3e0fb1b302e54e6",
"title": "Hello World",
"name": "Alex"
},
...
]
You can use the lean() method to return a pure JSON object (not a mongoose document) that you can then manipulate using lodash helper methods such as map(), like in the following example:
Post.find()
.populate("name")
.lean().exec(function (err, result) {
if(result){
var posts = _.map(result, function(p) {
p.name = p.postedBy.name;
p.postedBy = undefined;
return p;
});
console.log(posts);
}
});
You can disable the "__v" attribute in your Schema definitions by setting the versionKey option to false. For example:
var postSchema = new Schema({ ... attributes ... }, { versionKey: false });
As follow-up to your question on rearranging the order of the properties in the JSON, JS does not define the order of the properties of an object. However, you
can use both the JSON.parse() and JSON.stringify() methods to change the order, for example
var json = {
"name": "Alex",
"title": "Hello World",
"_id": "54cd6669d3e0fb1b302e54e6"
};
console.log(json);
//change order to _id, title, name
var changed = JSON.parse(JSON.stringify(json, ["_id","title","name"] , 4));
console.log(k);
Check the demo below.
var json = {
"name": "Alex",
"title": "Hello World",
"_id": "54cd6669d3e0fb1b302e54e6"
};
//change order to _id, title, name
var changed = JSON.parse(JSON.stringify(json, ["_id","title","name"] , 4));
pre.innerHTML = "original: " + JSON.stringify(json, null, 4) + "</br>Ordered: " + JSON.stringify(changed, null, 4);
<pre id="pre"></pre>