I've been having a problem trying to flatten the folders in this format:
for example we have this folder structure. The names should be changed as on the right side, whether as the format of each folder should be flattened.
The goal is for the folders to be flattened and each of their names should be for example:
If A has a subfolder B and subfolder C, C's name should be: A/B/C, B's name should be A/B.
{
id: "0",
name: null,
parentId: null,
folderType: "chatMessages",
folders: [
{
id: 3195588631115178,
name: "Testfolder",
parentId: null,
folderType: "chatMessages",
folders: [
{
id: "3195588620182363",
name: "Subfolder",
parentId: "3195588631115178",
folderType: "chatMessages",
folders: [
{
id: "3206824598737435",
name: "Interesting",
parentId: "3195588620182363",
folderType: "chat",
folders: [],
items: [
{
id: "3208409930553392",
name: "Message",
folderId: "3206824598737435",
updated: "2022-05-27T07:28:40.450Z",
frontendFolderId: null,
text: "<p>This is an HTML with Image.</p>",
keywords: "test",
subject: "What kind of subject",
slashCommand: "test",
language: "en-US",
setupItemId: "3208409930553392",
},
],
},
],
items: [
{
id: "3195595211854821",
name: "Message in subfolder",
folderId: "3195588620182363",
updated: "2022-05-19T12:05:39.503Z",
frontendFolderId: null,
text: "Message in subfolder",
keywords: "test",
subject: "Message in subfolder",
slashCommand: "sub",
language: "bn-BD",
setupItemId: "3195595211854821",
},
],
},
],
items: [],
},
],
items: [
{
id: "2888102250465731",
name: "bye",
folderId: null,
updated: "2022-05-25T11:15:36.367Z",
frontendFolderId: null,
text: "Thanks for contacting us. Please do not hesitate to contact us again if we can be of further assistance.",
keywords: "bye",
subject: null,
slashCommand: null,
language: null,
setupItemId: "2888102250465731",
},
],
}
UPDATE: How can I create an array of every item and their id, folderId and text properties?
I want to achieve this format:
{
id: "3195595211854821",
folderId: "3195588620182363",
text: "Message in subfolder",
}
Maybe something like this gets you started? Here flat is a recursive generator that yields array paths of name attributes -
function *flat({ name = "", folders = [], items = [] }) {
yield [name]
for (const x of [...folders, ...items])
for (const path of flat(x))
yield [name, ...path]
}
for (const path of flat(data))
console.log(path.join("/"))
/Testfolder
/Testfolder/Subfolder
/Testfolder/Subfolder/Interesting
/Testfolder/Subfolder/Interesting/Message
/Testfolder/Subfolder/Message in subfolder
/bye
Run the snippet below to verify the result in your own browser -
const data = {id:"0",name:null,parentId:null,folderType:"chatMessages",folders:[{id:3195588631115178,name:"Testfolder",parentId:null,folderType:"chatMessages",folders:[{id:"3195588620182363",name:"Subfolder",parentId:"3195588631115178",folderType:"chatMessages",folders:[{id:"3206824598737435",name:"Interesting",parentId:"3195588620182363",folderType:"chat",folders:[],items:[{id:"3208409930553392",name:"Message",folderId:"3206824598737435",updated:"2022-05-27T07:28:40.450Z",frontendFolderId:null,text:"<p>This is an HTML with Image.</p>",keywords:"test",subject:"What kind of subject",slashCommand:"test",language:"en-US",setupItemId:"3208409930553392",},],},],items:[{id:"3195595211854821",name:"Message in subfolder",folderId:"3195588620182363",updated:"2022-05-19T12:05:39.503Z",frontendFolderId:null,text:"Message in subfolder",keywords:"test",subject:"Message in subfolder",slashCommand:"sub",language:"bn-BD",setupItemId:"3195595211854821",},],},],items:[],},],items:[{id:"2888102250465731",name:"bye",folderId:null,updated:"2022-05-25T11:15:36.367Z",frontendFolderId:null,text:"Thanks for contacting us. Please do not hesitate to contact us again if we can be of further assistance.",keywords:"bye",subject:null,slashCommand:null,language:null,setupItemId:"2888102250465731",},],}
function *flat({ name = "", folders = [], items = [] }) {
yield [name]
for (const x of [...folders, ...items])
for (const path of flat(x))
yield [name, ...path]
}
for (const path of flat(data))
console.log(path.join("/"))
.as-console-wrapper { min-height: 100%; top: 0; }
Here's a second option that builds a path of nodes objects, not just the name properties. This gives the caller the option to use any node properties when constructing the output path -
function *flat(t = {}) {
yield [t]
for (const x of [...t.folders ?? [], ...t.items ?? []])
for (const path of flat(x))
yield [t, ...path]
}
for (const path of flat(data))
console.log(
path.map(node => `${node.name ?? ""}<#${node.id}>`).join("/")
)
<#0>
<#0>/Testfolder<#3195588631115178>
<#0>/Testfolder<#3195588631115178>/Subfolder<#3195588620182363>
<#0>/Testfolder<#3195588631115178>/Subfolder<#3195588620182363>/Interesting<#3206824598737435>
<#0>/Testfolder<#3195588631115178>/Subfolder<#3195588620182363>/Interesting<#3206824598737435>/Message<#3208409930553392>
<#0>/Testfolder<#3195588631115178>/Subfolder<#3195588620182363>/Message in subfolder<#3195595211854821>
<#0>/bye<#2888102250465731>
Run the snippet below to verify the result in your own browser -
const data = {id:"0",name:null,parentId:null,folderType:"chatMessages",folders:[{id:3195588631115178,name:"Testfolder",parentId:null,folderType:"chatMessages",folders:[{id:"3195588620182363",name:"Subfolder",parentId:"3195588631115178",folderType:"chatMessages",folders:[{id:"3206824598737435",name:"Interesting",parentId:"3195588620182363",folderType:"chat",folders:[],items:[{id:"3208409930553392",name:"Message",folderId:"3206824598737435",updated:"2022-05-27T07:28:40.450Z",frontendFolderId:null,text:"<p>This is an HTML with Image.</p>",keywords:"test",subject:"What kind of subject",slashCommand:"test",language:"en-US",setupItemId:"3208409930553392",},],},],items:[{id:"3195595211854821",name:"Message in subfolder",folderId:"3195588620182363",updated:"2022-05-19T12:05:39.503Z",frontendFolderId:null,text:"Message in subfolder",keywords:"test",subject:"Message in subfolder",slashCommand:"sub",language:"bn-BD",setupItemId:"3195595211854821",},],},],items:[],},],items:[{id:"2888102250465731",name:"bye",folderId:null,updated:"2022-05-25T11:15:36.367Z",frontendFolderId:null,text:"Thanks for contacting us. Please do not hesitate to contact us again if we can be of further assistance.",keywords:"bye",subject:null,slashCommand:null,language:null,setupItemId:"2888102250465731",},],}
function *flat(t = {}) {
yield [t]
for (const x of [...t.folders ?? [], ...t.items ?? []])
for (const path of flat(x))
yield [t, ...path]
}
for (const path of flat(data))
console.log(
path.map(node => `${node.name ?? ""}<#${node.id}>`).join("/")
)
.as-console-wrapper { min-height: 100%; top: 0; }
Related
I would like to explain my problem of the day.
Currently I perform a filter on an input which allows me to search the last name and first name it works really well
I have deleted a lot of things for a simpler reading of the code if there is a need to bring other element do not hesitate to ask
const {
data: packUsersData,
} = useQuery(
[
"pack",
id,
"users",
...(currentOperatorsIds.length ? currentOperatorsIds : []),
value,
],
async () => {
const getExpr = () => ({
$expr: {
$or: [
{
$regexMatch: {
input: {
$concat: ["$firstName", " ", "$lastName"],
},
regex: value,
options: "i",
},
},
{
$regexMatch: {
input: {
$concat: ["$lastName", " ", "$firstName"],
},
regex: value,
options: "i",
},
},
],
},
});
let res = await usersApi.getrs({
pagination: false,
query: {
"roles.name": "operator",
_id: { $nin: currentOperatorsIds },
deletedAt: null,
$or: value
? [
{
entities: [],
...getExpr(),
},
{
entities: { $in: id },
...getExpr(),
},
]
: [
{
entities: [],
},
{
entities: { $in: id },
},
],
},
populate: "entity",
sort: ["lastName", "firstName"],
});
{
refetchOnMount: true,
}
);
and so i find the read a bit too long have any idea how i could shorten all this?
thx for help.
You can reduce entities field $or condition, just concat the empty array and input id,
let res = await usersApi.getrs({
pagination: false,
query: {
"roles.name": "operator",
_id: { $nin: currentOperatorsIds },
deletedAt: null,
entities: { $in: [[], ...id] },
...getExpr()
},
populate: "entity",
sort: ["lastName", "firstName"]
});
If you want to improve the regular expression condition you can try the below approach without using $expr and aggregation operators,
create a function and set input searchKeyword and searchProperties whatever you want to in array of string
function getSearchContiion(searchKeyword, searchProperties) {
let query = {};
if (searchKeyword) {
query = { "$or": [] };
const sk = searchKeyword.trim().split(" ").map(n => new RegExp(n, "i"));
searchProperties.forEach(p => {
query["$or"].push({ [p]: { "$in": [...sk] } });
});
}
return query;
}
// EX:
console.log(getSearchContiion("John Doe", ["firstName", "lastName"]));
Use the above function in query
let res = await usersApi.getrs({
pagination: false,
query: Object.assign(
{
"roles.name": "operator",
_id: { $nin: currentOperatorsIds },
deletedAt: null,
entities: { $in: [[], ...id] }
},
getSearchContiion(value, ["firstName", "lastName"])
},
populate: "entity",
sort: ["lastName", "firstName"]
});
I made the following filter in hopes that I would be combining the results from all 3 $and arrays but it is only matching one of those blocks.
How can I combine the results of what would be returned from each $and array if conditions are met. Hopefully that's clear. I don't know what to call the $and array.
const filter = {
$or: [
{
$and: [
{ category: req.query.category },
{ tags: req.query.subCategory },
{contentType: req.query.contentType},
req.query.searchTerm !== ""
? {
name: {
$regex: "(?i)" + req.query.searchTerm + "(?-i)",
$options: "i",
},
}
: {},
],
$and: [
{ category: req.query.category },
{ tags: req.query.subCategory },
{contentType: req.query.contentType},
req.query.searchTerm !== ""
? {
description: {
$regex: "(?i)" + req.query.searchTerm + "(?-i)",
$options: "i",
},
}
: {},
],
$and: [
{ category: req.query.category },
{ tags: req.query.subCategory },
{contentType: req.query.contentType},
req.query.searchTerm !== ""
? {
tags: {
$regex: "(?i)" + req.query.searchTerm + "(?-i)",
$options: "i",
},
}
: {},
],
},
],
};
await Content.paginate(filter, options, (err, result) => {
if (err) {
res.status(500).send(err);
} else {
res.json(result);
}
});
EDIT: Below is an example of two entries that would be found in the database. The way it should work is it should use category, subCategory, and contentType to filter out the entries in the database so that what I have now are only the entries which have the same category, subCategory, and contentType as specified in req.query, I'll call this the firstFilterResult. From there, I am trying to search within firstFilterResult to see if I have entries that have name, tag, or description matches. So basically catgeory, subCategory and contentType are just used to narrow down the results so that I can find matches for name, tag, and description. My code above doesn't do exactly this but this is the idea behind it and I thought that what I have would do similar, but I guess I'm wrong.
contents: [
{
tags: [
'food',
'drinks',
'card',
'account'
],
_id: '1d13ff7m6db4d5417cd608f4',
name: 'THE NAME FOR THIS PIECE OF CONTENT',
description: 'In here I will begin to talk about...',
content_id: '5dbcb998ad4144390c244093',
contentType: 'quiz',
date: '2019-06-03T04:00:00.000Z',
category: 'food',
image: 'https://IMAGE.PNG',
__v: 0
},
{
tags: [
'computer',
'laptop'
],
_id: '7d1b940b1c9d44000025db8c',
name: 'THE NAME FOR THIS PIECE OF CONTENT',
description: 'This is another description',
content_id: '5f1b963d1c9d44000055db8d',
contentType: 'tool',
date: '2019-06-03T04:00:00.000Z',
category: 'money',
image: 'https://IMAGE.PNG',
__v: 0
}
]
I finally got it to work with this
const catFilter =
req.query.category !== "" ? { category: req.query.category } : {};
const subCatFilter =
req.query.subCategory !== "" ? { tags: req.query.subCategory } : {};
const typeFilter =
req.query.contentType !== ""
? { contentType: req.query.contentType }
: {};
const filter = {
$and: [
{
$or: [
{
name: {
$regex: req.query.searchTerm,
$options: "i",
},
},
{
description: {
$regex: req.query.searchTerm,
$options: "i",
},
},
{
tags: {
$regex: req.query.searchTerm,
$options: "i",
},
},
],
},
catFilter,
subCatFilter,
typeFilter,
],
};
Since each element of the $or contains the same 3 checks with a single one that varies, these can be separated out, and the $or is then only needed if a search term is specified.
Passing options:"i" makes the entire regex match case insensitive, so it is not necessary to surround the search string with (?i) and (?-i)
The following should build the filter that you are attempting, without using empty objects:
// base query that checks the common fields
var filter = {
category: req.query.category,
tags: req.query.subCategory,
contentType: req.query.contentType
};
// if a search term is provided, add in the additional critera
if (req.query.searchTerm !== "") {
var regex = {
$regex: req.query.searchTerm,
options:"i"
};
filter['$or'] = [
{ name: regex },
{ description: regex },
{ tags: regex }
]
}
If this doesn't obtain the results you're after, please edit the question and add in some sample documents so we can see the problem.
I need to modify a document inside an array that is inside another array.
I know MongoDB doesn't support multiple '$' to iterate on multiple arrays at the same time, but they introduced arrayFilters for that.
See: https://jira.mongodb.org/browse/SERVER-831
MongoDB's sample code:
db.coll.update({}, {$set: {“a.$[i].c.$[j].d”: 2}}, {arrayFilters: [{“i.b”: 0}, {“j.d”: 0}]})
Input: {a: [{b: 0, c: [{d: 0}, {d: 1}]}, {b: 1, c: [{d: 0}, {d: 1}]}]}
Output: {a: [{b: 0, c: [{d: 2}, {d: 1}]}, {b: 1, c: [{d: 0}, {d: 1}]}]}
Here's how the documents are set:
{
"_id" : ObjectId("5a05a8b7e0ce3444f8ec5bd7"),
"name" : "support",
"contactTypes" : {
"nonWorkingHours" : [],
"workingHours" : []
},
"workingDays" : [],
"people" : [
{
"enabled" : true,
"level" : "1",
"name" : "Someone",
"_id" : ObjectId("5a05a8c3e0ce3444f8ec5bd8"),
"contacts" : [
{
"_id" : ObjectId("5a05a8dee0ce3444f8ec5bda"),
"retries" : "1",
"priority" : "1",
"type" : "email",
"data" : "some.email#email.com"
}
]
}
],
"__v" : 0
}
Here's the schema:
const ContactSchema = new Schema({
data: String,
type: String,
priority: String,
retries: String
});
const PersonSchema = new Schema({
name: String,
level: String,
priority: String,
enabled: Boolean,
contacts: [ContactSchema]
});
const GroupSchema = new Schema({
name: String,
people: [PersonSchema],
workingHours: { start: String, end: String },
workingDays: [Number],
contactTypes: { workingHours: [String], nonWorkingHours: [String] }
});
I need to update a contact. This is what I tried using arrayFilters:
Group.update(
{},
{'$set': {'people.$[i].contacts.$[j].data': 'new data'}},
{arrayFilters: [
{'i._id': mongoose.Types.ObjectId(req.params.personId)},
{'j._id': mongoose.Types.ObjectId(req.params.contactId)}]},
function(err, doc) {
if (err) {
res.status(500).send(err);
}
res.send(doc);
}
);
The document is never updated and I get this response:
{
"ok": 0,
"n": 0,
"nModified": 0
}
What am I doing wrong?
So the arrayFilters option with positional filtered $[<identifier>] does actually work properly with the development release series since MongoDB 3.5.12 and also in the current release candidates for the MongoDB 3.6 series, where this will actually be officially released. The only problem is of course is that the "drivers" in use have not actually caught up to this yet.
Re-iterating the same content I have already placed on Updating a Nested Array with MongoDB:
NOTE Somewhat ironically, since this is specified in the "options" argument for .update() and like methods, the syntax is generally compatible with all recent release driver versions.
However this is not true of the mongo shell, since the way the method is implemented there ( "ironically for backward compatibility" ) the arrayFilters argument is not recognized and removed by an internal method that parses the options in order to deliver "backward compatibility" with prior MongoDB server versions and a "legacy" .update() API call syntax.
So if you want to use the command in the mongo shell or other "shell based" products ( notably Robo 3T ) you need a latest version from either the development branch or production release as of 3.6 or greater.
All this means is that the current "driver" implementation of .update() actually "removes" the necessary arguments with the definition of arrayFilters. For NodeJS this will be addressed in the 3.x release series of the driver, and of course "mongoose" will then likely take some time after that release to implement it's own dependencies on the updated driver, which would then no longer "strip" such actions.
You can however still run this on a supported server instance, by dropping back to the basic "update command" syntax usage, since this bypassed the implemented driver method:
const mongoose = require('mongoose'),
Schema = mongoose.Schema,
ObjectId = mongoose.Types.ObjectId;
mongoose.Promise = global.Promise;
mongoose.set('debug',true);
const uri = 'mongodb://localhost/test',
options = { useMongoClient: true };
const contactSchema = new Schema({
data: String,
type: String,
priority: String,
retries: String
});
const personSchema = new Schema({
name: String,
level: String,
priority: String,
enabled: Boolean,
contacts: [contactSchema]
});
const groupSchema = new Schema({
name: String,
people: [personSchema],
workingHours: { start: String, end: String },
workingDays: { type: [Number], default: undefined },
contactTypes: {
workingHours: { type: [String], default: undefined },
contactTypes: { type: [String], default: undefined }
}
});
const Group = mongoose.model('Group', groupSchema);
function log(data) {
console.log(JSON.stringify(data, undefined, 2))
}
(async function() {
try {
const conn = await mongoose.connect(uri,options);
// Clean data
await Promise.all(
Object.entries(conn.models).map(([k,m]) => m.remove() )
);
// Create sample
await Group.create({
name: "support",
people: [
{
"_id": ObjectId("5a05a8c3e0ce3444f8ec5bd8"),
"enabled": true,
"level": "1",
"name": "Someone",
"contacts": [
{
"type": "email",
"data": "adifferent.email#example.com"
},
{
"_id": ObjectId("5a05a8dee0ce3444f8ec5bda"),
"retries": "1",
"priority": "1",
"type": "email",
"data": "some.email#example.com"
}
]
}
]
});
let result = await conn.db.command({
"update": Group.collection.name,
"updates": [
{
"q": {},
"u": { "$set": { "people.$[i].contacts.$[j].data": "new data" } },
"multi": true,
"arrayFilters": [
{ "i._id": ObjectId("5a05a8c3e0ce3444f8ec5bd8") },
{ "j._id": ObjectId("5a05a8dee0ce3444f8ec5bda") }
]
}
]
});
log(result);
let group = await Group.findOne();
log(group);
} catch(e) {
console.error(e);
} finally {
mongoose.disconnect();
}
})()
Since that sends the "command" directly through to the server, we see the expected update does in fact take place:
Mongoose: groups.remove({}, {})
Mongoose: groups.insert({ name: 'support', _id: ObjectId("5a06557fb568aa0ad793c5e4"), people: [ { _id: ObjectId("5a05a8c3e0ce3444f8ec5bd8"), enabled: true, level: '1', name: 'Someone', contacts: [ { type: 'email', data: 'adifferent.email#example.com', _id: ObjectId("5a06557fb568aa0ad793c5e5") }, { _id: ObjectId("5a05a8dee0ce3444f8ec5bda"), retries: '1', priority: '1', type: 'email', data: 'some.email#example.com' } ] } ], __v: 0 })
{ n: 1,
nModified: 1,
opTime:
{ ts: Timestamp { _bsontype: 'Timestamp', low_: 3, high_: 1510364543 },
t: 24 },
electionId: 7fffffff0000000000000018,
ok: 1,
operationTime: Timestamp { _bsontype: 'Timestamp', low_: 3, high_: 1510364543 },
'$clusterTime':
{ clusterTime: Timestamp { _bsontype: 'Timestamp', low_: 3, high_: 1510364543 },
signature: { hash: [Object], keyId: 0 } } }
Mongoose: groups.findOne({}, { fields: {} })
{
"_id": "5a06557fb568aa0ad793c5e4",
"name": "support",
"__v": 0,
"people": [
{
"_id": "5a05a8c3e0ce3444f8ec5bd8",
"enabled": true,
"level": "1",
"name": "Someone",
"contacts": [
{
"type": "email",
"data": "adifferent.email#example.com",
"_id": "5a06557fb568aa0ad793c5e5"
},
{
"_id": "5a05a8dee0ce3444f8ec5bda",
"retries": "1",
"priority": "1",
"type": "email",
"data": "new data" // <-- updated here
}
]
}
]
}
So right "now"[1] the drivers available "off the shelf" don't actually implement .update() or it's other implementing counterparts in a way that is compatible with actually passing through the necessary arrayFilters argument. So if you are "playing with" a development series or release candiate server, then you really should be prepared to be working with the "bleeding edge" and unreleased drivers as well.
But you can actually do this as demonstrated in any driver, in the correct form where the command being issued is not going to be altered.
[1] As of writing on November 11th 2017 there is no "official" release of MongoDB or the supported drivers that actually implement this. Production usage should be based on official releases of the server and supported drivers only.
I had a similar use case. But my second level nested array doesn't have a key. While most examples out there showcase an example with arrays having a key like this:
{
"id": 1,
"items": [
{
"name": "Product 1",
"colors": ["yellow", "blue", "black"]
}
]
}
My use case is like this, without the key:
{
"colors": [
["yellow"],
["blue"],
["black"]
]
}
I managed to use the arrayfilters by ommiting the label of the first level of the array nest. Example document:
db.createCollection('ProductFlow')
db.ProductFlow.insertOne(
{
"steps": [
[
{
"actionType": "dispatch",
"payload": {
"vehicle": {
"name": "Livestock Truck",
"type": "road",
"thirdParty": true
}
}
},
{
"actionType": "dispatch",
"payload": {
"vehicle": {
"name": "Airplane",
"type": "air",
"thirdParty": true
}
}
}
],
[
{
"actionType": "store",
"payload": {
"company": "Company A",
"is_supplier": false
}
}
],
[
{
"actionType": "sell",
"payload": {
"reseller": "Company B",
"is_supplier": false
}
}
]
]
}
)
In my case, I want to:
Find all documents that have any steps with payload.vehicle.thirdParty=true and actionType=dispatch
Update the actions set payload.vehicle.thirdParty=true only for the actions that have actionType=dispatch.
My first approach was withour arrayfilters. But it would create the property payload.vehicle.thirdParty=true inside the steps with actionType store and sell.
The final query that updated the properties only inside the steps with actionType=dispatch:
Mongo Shell:
db.ProductFlow.updateMany(
{"steps": {"$elemMatch": {"$elemMatch": {"payload.vehicle.thirdParty": true, "actionType": "dispatch"}}}},
{"$set": {"steps.$[].$[i].payload.vehicle.thirdParty": false}},
{"arrayFilters": [ { "i.actionType": "dispatch" } ], multi: true}
)
PyMongo:
query = {
"steps": {"$elemMatch": {"$elemMatch": {"payload.vehicle.thirdParty": True, "actionType": "dispatch"}}}
}
update_statement = {
"$set": {
"steps.$[].$[i].payload.vehicle.thirdParty": False
}
}
array_filters = [
{ "i.actionType": "dispatch" }
]
NOTE that I'm omitting the label on the first array at the update statement steps.$[].$[i].payload.vehicle.thirdParty. Most examples out there will use both labels because their objects have a key for the array. I took me some time to figure that out.
Basically I have a mongodb collection called 'people'
whose schema is as follows:
people: {
name: String,
friends: [{firstName: String, lastName: String}]
}
Now, I have a very basic express application that connects to the database and successfully creates 'people' with an empty friends array.
In a secondary place in the application, a form is in place to add friends. The form takes in firstName and lastName and then POSTs with the name field also for reference to the proper people object.
What I'm having a hard time doing is creating a new friend object and then "pushing" it into the friends array.
I know that when I do this via the mongo console I use the update function with $push as my second argument after the lookup criteria, but I can't seem to find the appropriate way to get mongoose to do this.
db.people.update({name: "John"}, {$push: {friends: {firstName: "Harry", lastName: "Potter"}}});
Assuming, var friend = { firstName: 'Harry', lastName: 'Potter' };
There are two options you have:
Update the model in-memory, and save (plain javascript array.push):
person.friends.push(friend);
person.save(done);
or
PersonModel.update(
{ _id: person._id },
{ $push: { friends: friend } },
done
);
I always try and go for the first option when possible, because it'll respect more of the benefits that mongoose gives you (hooks, validation, etc.).
However, if you are doing lots of concurrent writes, you will hit race conditions where you'll end up with nasty version errors to stop you from replacing the entire model each time and losing the previous friend you added. So only go to the latter when it's absolutely necessary.
The $push operator appends a specified value to an array.
{ $push: { <field1>: <value1>, ... } }
$push adds the array field with the value as its element.
Above answer fulfils all the requirements, but I got it working by doing the following
var objFriends = { fname:"fname",lname:"lname",surname:"surname" };
People.findOneAndUpdate(
{ _id: req.body.id },
{ $push: { friends: objFriends } },
function (error, success) {
if (error) {
console.log(error);
} else {
console.log(success);
}
});
)
Another way to push items into array using Mongoose is- $addToSet, if you want only unique items to be pushed into array. $push operator simply adds the object to array whether or not the object is already present, while $addToSet does that only if the object is not present in the array so as not to incorporate duplicacy.
PersonModel.update(
{ _id: person._id },
{ $addToSet: { friends: friend } }
);
This will look for the object you are adding to array. If found, does nothing. If not, adds it to the array.
References:
$addToSet
MongooseArray.prototype.addToSet()
Use $push to update document and insert new value inside an array.
find:
db.getCollection('noti').find({})
result for find:
{
"_id" : ObjectId("5bc061f05a4c0511a9252e88"),
"count" : 1.0,
"color" : "green",
"icon" : "circle",
"graph" : [
{
"date" : ISODate("2018-10-24T08:55:13.331Z"),
"count" : 2.0
}
],
"name" : "online visitor",
"read" : false,
"date" : ISODate("2018-10-12T08:57:20.853Z"),
"__v" : 0.0
}
update:
db.getCollection('noti').findOneAndUpdate(
{ _id: ObjectId("5bc061f05a4c0511a9252e88") },
{ $push: {
graph: {
"date" : ISODate("2018-10-24T08:55:13.331Z"),
"count" : 3.0
}
}
})
result for update:
{
"_id" : ObjectId("5bc061f05a4c0511a9252e88"),
"count" : 1.0,
"color" : "green",
"icon" : "circle",
"graph" : [
{
"date" : ISODate("2018-10-24T08:55:13.331Z"),
"count" : 2.0
},
{
"date" : ISODate("2018-10-24T08:55:13.331Z"),
"count" : 3.0
}
],
"name" : "online visitor",
"read" : false,
"date" : ISODate("2018-10-12T08:57:20.853Z"),
"__v" : 0.0
}
First I tried this code
const peopleSchema = new mongoose.Schema({
name: String,
friends: [
{
firstName: String,
lastName: String,
},
],
});
const People = mongoose.model("person", peopleSchema);
const first = new Note({
name: "Yash Salvi",
notes: [
{
firstName: "Johnny",
lastName: "Johnson",
},
],
});
first.save();
const friendNew = {
firstName: "Alice",
lastName: "Parker",
};
People.findOneAndUpdate(
{ name: "Yash Salvi" },
{ $push: { friends: friendNew } },
function (error, success) {
if (error) {
console.log(error);
} else {
console.log(success);
}
}
);
But I noticed that only first friend (i.e. Johhny Johnson) gets saved and the objective to push array element in existing array of "friends" doesn't seem to work as when I run the code , in database in only shows "First friend" and "friends" array has only one element !
So the simple solution is written below
const peopleSchema = new mongoose.Schema({
name: String,
friends: [
{
firstName: String,
lastName: String,
},
],
});
const People = mongoose.model("person", peopleSchema);
const first = new Note({
name: "Yash Salvi",
notes: [
{
firstName: "Johnny",
lastName: "Johnson",
},
],
});
first.save();
const friendNew = {
firstName: "Alice",
lastName: "Parker",
};
People.findOneAndUpdate(
{ name: "Yash Salvi" },
{ $push: { friends: friendNew } },
{ upsert: true }
);
Adding "{ upsert: true }" solved problem in my case and once code is saved and I run it , I see that "friends" array now has 2 elements !
The upsert = true option creates the object if it doesn't exist. default is set to false.
if it doesn't work use below snippet
People.findOneAndUpdate(
{ name: "Yash Salvi" },
{ $push: { friends: friendNew } },
).exec();
An easy way to do that is to use the following:
var John = people.findOne({name: "John"});
John.friends.push({firstName: "Harry", lastName: "Potter"});
John.save();
In my case, I did this
const eventId = event.id;
User.findByIdAndUpdate(id, { $push: { createdEvents: eventId } }).exec();
Push to nested field - use a dot notation
For anyone wondering how to push to a nested field when you have for example this Schema.
const UserModel = new mongoose.schema({
friends: {
bestFriends: [{ firstName: String, lastName: String }],
otherFriends: [{ firstName: String, lastName: String }]
}
});
You just use a dot notation, like this:
const updatedUser = await UserModel.update({_id: args._id}, {
$push: {
"friends.bestFriends": {firstName: "Ima", lastName: "Weiner"}
}
});
This is how you could push an item - official docs
const schema = Schema({ nums: [Number] });
const Model = mongoose.model('Test', schema);
const doc = await Model.create({ nums: [3, 4] });
doc.nums.push(5); // Add 5 to the end of the array
await doc.save();
// You can also pass an object with `$each` as the
// first parameter to use MongoDB's `$position`
doc.nums.push({
$each: [1, 2],
$position: 0
});
doc.nums;
// This is the my solution for this question.
// I want to add new object in worKingHours(array of objects) -->
workingHours: [
{
workingDate: Date,
entryTime: Date,
exitTime: Date,
},
],
// employeeRoutes.js
const express = require("express");
const router = express.Router();
const EmployeeController = require("../controllers/employeeController");
router
.route("/:id")
.put(EmployeeController.updateWorkingDay)
// employeeModel.js
const mongoose = require("mongoose");
const validator = require("validator");
const employeeSchema = new mongoose.Schema(
{
name: {
type: String,
required: [true, "Please enter your name"],
},
address: {
type: String,
required: [true, "Please enter your name"],
},
email: {
type: String,
unique: true,
lowercase: true,
required: [true, "Please enter your name"],
validate: [validator.isEmail, "Please provide a valid email"],
},
phone: {
type: String,
required: [true, "Please enter your name"],
},
joiningDate: {
type: Date,
required: [true, "Please Enter your joining date"],
},
workingHours: [
{
workingDate: Date,
entryTime: Date,
exitTime: Date,
},
],
},
{
toJSON: { virtuals: true },
toObject: { virtuals: true },
}
);
const Employee = mongoose.model("Employee", employeeSchema);
module.exports = Employee;
// employeeContoller.js
/////////////////////////// SOLUTION IS BELOW ///////////////////////////////
// This is for adding another day, entry and exit time
exports.updateWorkingDay = async (req, res) => {
const doc = await Employee.findByIdAndUpdate(req.params.id, {
$push: {
workingHours: req.body,
},
});
res.status(200).json({
status: "true",
data: { doc },
});
};
https://www.youtube.com/watch?v=gtUPPO8Re98
I ran into this issue as well. My fix was to create a child schema. See below for an example for your models.
---- Person model
const mongoose = require('mongoose');
const SingleFriend = require('./SingleFriend');
const Schema = mongoose.Schema;
const productSchema = new Schema({
friends : [SingleFriend.schema]
});
module.exports = mongoose.model('Person', personSchema);
***Important: SingleFriend.schema -> make sure to use lowercase for schema
--- Child schema
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const SingleFriendSchema = new Schema({
Name: String
});
module.exports = mongoose.model('SingleFriend', SingleFriendSchema);
i am trying to push array in document array my collection is
{
"_id": "58eed81af6f8e3788de703f9",
"first_name": "abc",
"vehicles": {
"exhibit": "18",
"title": "Motor Velicle Information for Donald French",
"details": [
{
"year": "",
"make_model": "",
"registered_owner": "",
"license_number": "",
"date_of_purchase": "",
"purchase_price": ""
}
]
}
}
so what i want is to push data in details for that i had try like this
Licensee.update({"_id":"58eed81af6f8e3788de703f9"},{
$push:{
"vehicles.details":data
}
},function(err,data){
if(!err)
{
console.log('data',data);
}
else
{
console.log('err',err);
}
});
and for this i create one schema i don't know is right or not
var licSchema = new SimpleSchema({
"_id":{
type:String,
label:"_id",
optional: false,
},
"vehicles.details.year": {
type: String,
label: "year",
optional: true,
},
"vehicles.details.make_model": {
type: String,
label: "make_model",
optional: true,
}
});
where is my fault please give me solution .
Error Uncaught Error: After filtering out keys not in the schema, your modifier is now empty
You can try this. AddToSet should be the right way.
const schema = new SimpleSchema({
"vehicles.details.$.year": {
type: String,
label: "year",
optional: true,
},
"vehicles.details.$.make_model": {
type: String,
label: "make_model",
optional: true,
}
});
Licensee.update({"_id":"58eed81af6f8e3788de703f9"},{
$addToSet:{
"vehicles.details": data
}
});