Pushing entry into multilevel nested array in MongoDB via mongoose - javascript

I am using mongoose in nodejs(express) in backend. My array structure has THREE levels. At third level, some files are present. But I need to add entries at any level as per user demand.
[
{
"name": "A folder at",
"route": "level1_a"
},
{
"name":"Another folder at Level1",
"route": "level1_b",
"children":
[
{
"name": "A folder at Level2",
"route": "level1_b/level2_a",
"children":
[
{
"name": "A folder at Level3",
"route": "level1_b/level2_a/level3_a",
"children":
[
{
"name": "A file at last level",
"route": "level1_b/level2_a/level3_a/file1"
},
{
"name": "Add a new File",
"route":"level1_b/level2_a/level3_a/new_file"
}
]
},
{
"name": "Add Folder at Level3",
"route":"level1_b/level2_a/new_folder"
}
]
},
{
"name": "Add Folder at level2",
"route":"level1_b/new_folder"
}
]
},
{
"name": "Add Folder at Level1",
"route":"new_folder"
}
]
Now I have to add an entry at a specified position. Suppose at level2, I need to add a folder. For adding, two parameters are sent from angular to the backend. These will be 'name' and a 'route'. So my entry would be having {name: 'Products', route: 'level1_a/products'} and similarily should be placed at correct position i.e. inside the children of level1_a.
My backend has a schema which would be like:
const navSchema = mongoose.Schema({
name:{type:String,required:true},
route:{type:String},
children:{
type: {
name:{type:String,required:true},
route:{type:String},
}}
});
module.exports = mongoose.model('NatItems',navSchema);
And the API would be like:
router.post('/navlist',(req,res,next)=>{
const name= req.body.folder;
const route= req.body.url;
console.log(folder,url);//it will be required parameters like name: 'Products', route:'level1_a/products'
//let pathArray = route.split('/'); //if you want you can split the urls in the array
//Help me write the code here
res.status(201).json({
message:"Post added successfully!"
})
})
Please help me in adding entries in db. I know navlist.save() adds an entry directly but I am not able to add entries in a nested manner.
PS: I can't change the array structure because this array is easily read by angular and a complete navigation menu is made!! I am working for first time in nodejs and mongoose, so I am having difficulty in writing code with mongoose function.

For the scenario you've provided ({name: 'Products', route: 'level1_a/products'}) the update statement is pretty straightforward and looks like this:
Model.update(
{ route: "level1_a" },
{ $push: { children: {name: 'Products', route: 'level1_a/products'} } })
Things are getting a little bit more complicated when there are more than two segments in the incoming route, e.g.
{ "name": "Add a new File", "route":"level1_b/level2_a/level3_a/new_file2" };
In such case you need to take advantage of the positional filtered operator and build arrayFilters and your query becomes this:
Model.update(
{ "route": "level1_b"},
{
"$push": {
"children.$[child0].children.$[child1].children": {
"name": "Add a new File",
"route": "level1_b/level2_a/level3_a/new_file2"
}
}
},
{
"arrayFilters": [
{
"child0.route": "level1_b/level2_a"
},
{
"child1.route": "level1_b/level2_a/level3_a"
}
]
})
So you need a function which loops through the route and builds corresponding update statement along with options:
let obj = { "name": "Add a new File", "route":"level1_b/level2_a/level3_a/new_file2" };
let segments = obj.route.split('/');;
let query = { route: segments[0] };
let update, options = {};
if(segments.length === 2){
update = { $push: { children: obj } }
} else {
let updatePath = "children";
options.arrayFilters = [];
for(let i = 0; i < segments.length -2; i++){
updatePath += `.$[child${i}].children`;
options.arrayFilters.push({ [`child${i}.route`]: segments.slice(0, i + 2).join('/') });
}
update = { $push: { [updatePath]: obj } };
}
console.log('query', query);
console.log('update', update);
console.log('options', options);
So you can run:
Model.update(query, update, options);

Related

I am having trouble appending dynamic data to my JSON file

I have been having a bit of trouble appending new dynamic data to a JSON file. To sum up my project, I take in the projectName from an input form at the /new page.
My API is then using the node.js's fs module to create a new JSON file with which I can then append the new dynamic data upon subsequential requests to my form. The variables are 1) projectName (is taken in from my form), 2) activeUser (which is programmed in through an environmental variable), 3) is the date of the request which I am acquiring through a timestamp variable with this function:
const timestamp = (JSON.parse(JSON.stringify(new Date())));
All three of these variables seem to print correctly for 2 subsequent requests and then on the third form submission there seems to be no new data appending to the JSON file. However i am relatively new to node.js and I can't seem to figure out where I am messing this up.
This is my API
pages/api/demos/index.js
import dbConnect from '../../../lib/dbConnect';
import Demo from '../../../models/Demo';
import fs from 'fs';
export default async function handler(req, res) {
const {
query: { id },
method,
} = req
await dbConnect()
switch (method) {
case 'POST':
try {
//check if file exist
if (!fs.existsSync('projects.json')) {
//create new file if not exist
fs.closeSync(fs.openSync('projects.json', 'w'));
}
// read file
const timestamp = (JSON.parse(JSON.stringify(new Date())));
const newFileName = req.body.projectName;
const activeUser = process.env.ACTIVE_USERNAME;
const file = fs.readFileSync('projects.json')
const data = {
"projects": [
{
"username": activeUser,
"pages": [
{
"display": "routes",
"subpages": [
{
"date": timestamp,
"route": newFileName,
"display": newFileName
}
]
}
]
}
]
}
//check if file is empty
if (file.length == 0) {
//add data to json file
fs.writeFileSync("projects.json", JSON.stringify([data]))
} else {
//append data to jso file
const json = JSON.parse(file.toString())
//add json element to json object
json.push(data);
fs.appendFileSync("projects.json", JSON.stringify(data))
}
const demo = await Demo.create(
req.body
)
res.status(201).json({ success: true, data: demo })
} catch (error) {
res.status(400).json({ success: false })
}
break
default:
res.status(400).json({ success: false })
break
}
}
After the first form submission my JSON file projects.json looks like
[
{
"projects": [
{
"username": "projectmikey",
"pages": [
{
"display": "routes",
"subpages": [
{
"date": "2022-09-12T19:03:09.547Z",
"route": "1",
"display": "1"
}
]
}
]
}
]
}
]
and then after the 2nd form submission
[
{
"projects": [
{
"username": "projectmikey",
"pages": [
{
"display": "routes",
"subpages": [
{
"date": "2022-09-12T19:03:09.547Z",
"route": "1",
"display": "1"
}
]
}
]
}
]
}
]{
"projects": [
{
"username": "projectmikey",
"pages": [
{
"display": "routes",
"subpages": [
{
"date": "2022-09-12T19:03:24.466Z",
"route": "2",
"display": "2"
}
]
}
]
}
]
}
Oddly it seems to work for two form submissions and then the data stops appending to my file. This is after the third attempt, (no change to the file)
[
{
"projects": [
{
"username": "projectmikey",
"pages": [
{
"display": "routes",
"subpages": [
{
"date": "2022-09-12T19:03:09.547Z",
"route": "1",
"display": "1"
}
]
}
]
}
]
}
]{
"projects": [
{
"username": "projectmikey",
"pages": [
{
"display": "routes",
"subpages": [
{
"date": "2022-09-12T19:03:24.466Z",
"route": "2",
"display": "2"
}
]
}
]
}
]
}
It seems to stop working at all when I remove the pair of brackets around the initial JSON object. The line I am refering to is fs.writeFileSync("projects.json", JSON.stringify([data]))
I could really use another pair of eyes on this so I can see where I am messing this up! lol Thanks in advance for your time...
Although it feels like you are "appending" to the file, you are actually doing something more complicated.
e.g. before state:
[ "one", "two" ]
desired after-state:
[ "one", "two", "three" ]
Notice that you can't just append text to the before-state JSON because there's already that pesky ] terminating the whole object.
Some failed attempts might look like:
failed attempt to append another entire array
[ "one", "two" ][ "three" ]
This is invalid because there are two root objects.
failed attempt to append just the rest of the array
[ "one", "two" ], "three" ]
That's no good either. The ] at the end of the original file needs to be overwritten or removed, so there's no way to just append. I suppose technically you could seek to the position of the final ] and then continue writing an incomplete object from there. But this is very awkward to remove the final ] from the source and to remove the initial [ from the chunk you're trying to append. It's just a difficult approach.
What you actually want to do is:
read the entire JSON file
parse the JSON into a JavaScript object (or create an empty object if the file didn't exist)
Modify the JavaScript object as necessary (e.g. push into the array to add another element)
stringify the JavaScript object into new JSON
overwrite the entire file with the new JSON.
/* In Node.js:
const fs = require('fs');
try {
initialJSON = fs.readFileSync('example.json');
} catch (ignore) {
initialJSON = '[]';
}
*/
/* Mocked for this example: */
initialJSON = '["one","two"]';
// Common
obj = JSON.parse(initialJSON);
obj.push("three");
finalJSON = JSON.stringify(obj);
/* In Node.js:
fs.writeFileSync('example.json', finalJSON);
*/
/* Mocked for this example: */
console.log(finalJSON);

Construct a JSON Payload in javascript

I have to construct a JSON payload that looks like this, can someone help me? I am able to get the straight forward one but unable to build a nested payload. How do I go about adding more nested keys, one inside the other. Also some of the keys and values are dynamic and have to replaced with variables.
{
"format_version": "0.2.19",
"alliances": {
"xyz": {
"environments": {
"prd": {
"teams": {
"abc": {
"action": "edit",
"team": "abc",
"projects": {
"prjabc": {
"project": "prjabc",
"cost_center": "0",
"custom_iam_policies": [],
"iam": {
"view_group_email_name": "abc#email.com",
"sre_admin_group_email_name": "xyz#email.com"
},
"allowed_apis": [
"api1",
"api2"
],
"networks": {
"network1": {
"flags": [
"VM"
],
"region": "sample-region",
"preferred-suffix": "routable"
}
}
}
}
}
}
}
}
}
}
}
Let say you have an object as such
items = {
foo: "bar",
something: "useful"
}
and if you wanted to add other properties or add nested object you can do so like this
subitems = { name: "Johnson" };
items['subitem'] = subitems;
After you've added and finalized the object, you can just use JSON.stringify(items) to convert your object into "payload"

How to get all values of given specific keys (for e.g: name) without loop from json?

I want to fetch all the names and label from JSON without loop. Is there a way to fetch with any filter method?
"sections": [
{
"id": "62ee1779",
"name": "Drinks",
"items": [
{
"id": "1902b625",
"name": "Cold Brew",
"optionSets": [
{
"id": "45f2a845-c83b-49c2-90ae-a227dfb7c513",
"label": "Choose a size",
},
{
"id": "af171c34-4ca8-4374-82bf-a418396e375c",
"label": "Additional Toppings",
},
],
},
]
}
When you say "without loops" I take it as without For Loops. because any kind of traversal of arrays, let alone nested traversal, involve iterating.
You can use the reduce method to have it done for you internally and give you the format you need.
Try this :
const data = {
sections: [
{
id: "62ee1779",
name: "Drinks",
items: [
{
id: "1902b625",
name: "Cold Brew",
optionSets: [
{
id: "45f2a845-c83b-49c2-90ae-a227dfb7c513",
label: "Choose a size"
},
{
id: "af171c34-4ca8-4374-82bf-a418396e375c",
label: "Additional Toppings"
}
]
}
]
}
]
};
x = data.sections.reduce((acc, ele) => {
acc.push(ele.name);
otherName = ele.items.reduce((acc2, elem2) => {
acc2.push(elem2.name);
label = elem2.optionSets.reduce((acc3, elem3) => {
acc3.push(elem3.label);
return acc3;
}, []);
return acc2.concat(label);
}, []);
return acc.concat(otherName);
}, []);
console.log(x);
Go ahead and press run snippet to see if this matches your desired output.
For More on info reduce method
In the context of cJSON
yes, we can fetch the key value for any of the object.
1 - each key value is pointed by one of the objects. will simply fetch that object and from there will get the key value.
In the above case for
pre-requisition: root must contain the json format and root must be the cJSON pointer. if not we can define it and use cJSON_Parse() to parse the json.
1st name object is "sections" will use
cJSON *test = cJSON_GetObjectItem(root, "sections");
char *name1 = cJSON_GetObjectItem(test, "name" )->valuestring;
2nd name key value
cJSON *test2 = cJSON_GetObjectItem(test, "items");
char *name2 = cJSON_GetObjectItem(tes2, "name")->valuestring;
likewise, we can do for others as well to fetch the key value.

Insert object into MongoDB object's array

I'm trying to add an object into an empty array that is stored in one of my collections.
Currently this is how I have my collection setup:
[
{
"name": "user_added",
"DRGs": []
},
...
]
How can I insert an object into the collection so that it looks like this;
[
{
"name": "user_added",
"DRGs": [
{
"code": "491",
"name": "Back & neck procedures"
}
]
},
...
]
Check out $push documentation.
You should be able to accomplish your goal with the following:
var collectionName = 'users'; // or whatever your actual collection name is
var objectToPush = {
code: "491",
name: "Back & neck procedures"
};
db.collection(collectionName).updateOne(
{"name": "user_added"},
{ $push: { "DRGS": objectToPush }}
);

Add custom attribute to waterline model

Since nested population isn't available I need to pass my custom attributes manually. In my specific case this means: a customer has many projects, a project has many contributors.
Customer.find().populate('projects').exec(function(err, customer) {
Response looks like
[
{
"projects": [
{ "name": "First project" }
],
"customer": "John Doe"
},
{
"projects": [
{ "name": "Another project" },
{ "name": "And another one" }
],
"customer": "Susan Doe"
}
]
I'm iterating through the projects and want to attach a contributors attribute. I've tried
customer.forEach(function(customer, index) {
customer.projects.forEach(function(project, index) {
ProjectContributor.find({
project: project.id
}).exec(function(err, contributor) {
project.contributors = contributors;
});
But project.contributors is still undefined. Why? And how to attach these custom attributes?
There are many errors in you code.
Customer.find().populate('projects').exec(function(err, customers) {
customers.forEach(function(customer, index) {
customer.projects.forEach(function(project, index) {
ProjectContributor.findOne({project: project.id}) // use findOne since you only want one project at a time
.populate('contributors')
.exec(function(err, projectContributor) {
project.contributors = projectContributor.contributors; // contributors is in projectContributor
});
});
});
});

Categories

Resources