Mongoose error findByIdAndUpdate fails in cast - node.js

Trying to update a document using findByIdAndUpdate, i get an error that i don't understand.
console.log(req.body);
var data = req.body;
data._id = undefined;
Package.findByIdAndUpdate(req.params.id, data, function (err, pkg) {
if (err) {
console.log(err.stack);
return next(restify.InternalServerError(err));
}
res.json(pkg);
next();
});
I get the following error:
TypeError: Cannot read property '_id' of undefined
at ObjectId.cast (/home/ubuntu/workspace/server/node_modules/mongoose/lib/schema/objectid.js:109:12)
at ObjectId.castForQuery (/home/ubuntu/workspace/server/node_modules/mongoose/lib/schema/objectid.js:165:17)
at Query._castUpdateVal (/home/ubuntu/workspace/server/node_modules/mongoose/lib/query.js:2009:17)
at Query._walkUpdatePath (/home/ubuntu/workspace/server/node_modules/mongoose/lib/query.js:1969:25)
at Query._castUpdate (/home/ubuntu/workspace/server/node_modules/mongoose/lib/query.js:1865:23)
at castDoc (/home/ubuntu/workspace/server/node_modules/mongoose/lib/query.js:2032:18)
at Query._findAndModify (/home/ubuntu/workspace/server/node_modules/mongoose/lib/query.js:1509:17)
at Query.findOneAndUpdate (/home/ubuntu/workspace/server/node_modules/mongoose/node_modules/mquery/lib/mquery.js:2056:15)
at Function.Model.findOneAndUpdate (/home/ubuntu/workspace/server/node_modules/mongoose/lib/model.js:1250:13)
at Function.Model.findByIdAndUpdate (/home/ubuntu/workspace/server/node_modules/mongoose/lib/model.js:1344:32)
I have verified that the id is valid, data is a valid object as well.
My model:
mongoose.model('Package', {
name: {
required: true,
type: String
},
servers: [mongoose.Schema.Types.ObjectId],
packageType: {
type: String,
enum: ['package', 'subscription']
},
subscriptionPeriodInDays: Number,
pointsIncluded: Number,
price: Number,
rank: String,
data: mongoose.Schema.Types.Mixed //For custom solutions
});
The log also prints a valid data object
{
name: 'Your Package',
packageType: 'subscription',
subscriptionPeriodInDays: 30,
pointsIncluded: 10000,
price: 10,
rank: 'Donator',
_id: undefined,
__v: 0,
servers: [],
description: '<p>test</p>\n'
}
I have tried to step trough with the debugger but i couldn't find a reason for this.

As Raleigh said, you need to remove _id field. You can do it by delete data._id; instead of data._id = undefined;.

I believe Mongoose is trying to set the value of _id to undefined since the _id value is still getting passed in via the data object.
Try removing the line data._id = undefined; before you update the model or completely remove the _id field from the data object.

Related

Cast to ObjectId failed for value "6283201d60c794631cd1ba33\n" (type string) at path "_id" for model "Post"

I'm working on a social media project and getting this getting when I'm sending like/Unlike post request
CastError: Cast to ObjectId failed for value "6283\n" (type string) at path "_id" for model "Post"
at model.Query.exec (E:\social-media-app-mern\node_modules\mongoose\lib\query.js:4639:21)
at model.Query.Query.then (E:\social-media-app-mern\node_modules\mongoose\lib\query.js:4738:15)
at processTicksAndRejections (node:internal/process/task_queues:96:5) {
messageFormat: undefined,
stringValue: '"6283\n"',
kind: 'ObjectId',
value: '6283\n',
path: '_id',
reason: BSONTypeError: Argument passed in must be a string of 12 bytes or a string of
24 hex characters or an integer
at new BSONTypeError (E:\social-media-app-mern\node_modules\bson\lib\error.js:41:28)
at new ObjectId (E:\social-media-app-mern\node_modules\bson\lib\objectid.js:66:23) at castObjectId (E:\social-media-app-mern\node_modules\mongoose\lib\cast\objectid.js:25:12)
at ObjectId.cast (E:\social-media-app-mern\node_modules\mongoose\lib\schema\objectid.js:247:12)
at ObjectId.SchemaType.applySetters (E:\social-media-app-mern\node_modules\mongoose\lib\schematype.js:1135:12)
at ObjectId.SchemaType._castForQuery (E:\social-media-app-mern\node_modules\mongoose\lib\schematype.js:1567:15)
at ObjectId.SchemaType.castForQuery (E:\social-media-app-mern\node_modules\mongoose\lib\schematype.js:1557:15)
at ObjectId.SchemaType.castForQueryWrapper (E:\social-media-app-mern\node_modules\mongoose\lib\schematype.js:1534:20)
at cast (E:\social-media-app-mern\node_modules\mongoose\lib\cast.js:336:32)
at model.Query.Query.cast (E:\social-media-app-mern\node_modules\mongoose\lib\query.js:5062:12),
valueType: 'string'
}
routes :
const express = require("express");
const { createPost, likeAndUnlikePost } = require("../controllers/post");
const { isAuthenticated } = require("../middlewares/auth");
const router = express.Router();
router.route("/post/:id").get(isAuthenticated, likeAndUnlikePost);
module.exports = router;
models:
const mongoose = require("mongoose");
const postSchema = new mongoose.Schema({
caption: String,
image: {
public_id: String,
url: String,
},
owner: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
},
createdAt: {
type: Date,
default: Date.now,
},
likes: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "User",
},
],
comments: [
{
user: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
},
comment:{
type: String,
required: true,
}
},
],
});
module.exports = mongoose.model("Post", postSchema);
likeAndUnlikePost:
try {
const post = await Post.findById(req.params.id);
if (!post) {
return res.status(404).json({
success: false,
message: "Post not found",
});
}
if (post.likes.includes(req.user._id)) {
const index = post.likes.indexOf(req.user._id);
post.likes.splice(index, 1);
await post.save();
return res.status(200).json({
success: true,
message: "Post Unliked",
});
} else {
post.likes.push(req.user._id);
await post.save();
return res.status(200).json({
success: true,
message: "Post Liked",
});
}
} catch (error) {
res.status(500).json({
success: false,
message: error.message,
});
console.error(error) }
};
First verify if the _id exists. Then try converting the _id which is string, to ObjectId like
mongoose.Types.ObjectId(req.params.id);
Alright my friend, I see the problem!
Unlike the other answers, I actually ran your code.
Seems like it's working just fine as it is.
You don't need to change a thing in it!!!
One issue though,
look at the error you got:
Cast to ObjectId failed for value "6283\n"
What is this value of "6283\n"? That's the problem!
It seems like you have a user with an id of "6283\n".
Which means, it contains numbers AND a white-space, so the white-space makes it as string.
So, while I said that there is NO problem with your code, there might be a problem with your User Model, which you haven't shown us in this thread.
The Answer - How to fix it
Because I haven't seen your User Model, I'll make 2 guesses:
You wanted/intended your User _id field to be of type ObjectId. If so, find out why you have a user with a weird _id value of "6283\n", cause basically it's two mistakes: A) Your _id is made up of numbers, i.e. Number type, while wanting ObjectId type. B) You got a 100% accidental space that slipped inside there. Find out how it got there.
You wanted/intended your User _id field to be of type Number. If so, then this again splits into two problems: A) Your user _id still contains a white-space, which is what I assume 100% accidental. Find out its source. B) Your user _id is made up of numbers, which is what you want, but look at the array field inside your Post model, which contains a ref to your user. You state there that the User's _id field is of type ObjectId, and not a Number, hence the mismatch.
when i encounterd this error it was because i was using the get method instead of the post method
Somewhere in your code you have assigned string value to _id (which is of type ObjectId)

Is there a way to validate a UUID inserted in a mongoDB using mongo's validator?

I am using migrate-mongo for managing my database migration and I am trying to create a new migration that create a collection with a validator and insert values in it. I want to use a UUID for the _id property and I am using the uuid-mongodb library to generate it. My problem is that I am not able to set the bsonType of my _id in the validator without causing the data insertion failure. Is there any way to make sure that the id of the documents inserted in the collection is a UUID? I know that mongoose could help me to solve this issue, but I would like the validation to be done at the database level. Also, when I do not specify the _id's bsonType in the validator, the insertion works, it fails validation only when I specify it.
Here is my migration code
const MUUID = require("uuid-mongodb");
module.exports = {
async up(db) {
//Will use https://docs.mongodb.com/manual/tutorial/model-tree-structures-with-materialized-paths/
await db.createCollection("itemCategories", {
validator: {
$jsonSchema: {
required: ["name"],
bsonType: "object",
properties: {
_id: {"object"}, //I also tried with binData
name: {
bsonType: "string",
maxLength: 50,
},
path: {
bsonType: ["string", "null"],
pattern: "^,([^,]+,)+$"
}
},
additionalProperties: false,
}
},
});
await db.collection("itemCategories").createIndex({"name": 1}, {unique: true});
await db.collection("itemCategories").insertMany([
{_id: MUUID.v4(), name: "Sport", path: null},
{_id: MUUID.v4(), name: "Tool", path: null},
{_id: MUUID.v4(), name: "Entertainment", path: null}
]);
},
async down(db) {
await db.dropCollection("itemCategories");
}
};
And here is the error I get when running it
ERROR: Could not migrate up 20210627041314-create-categories.js: Document failed validation BulkWriteError: Document failed validation
at OrderedBulkOperation.handleWriteError (C:\Users\username\projectDirectory\node_modules\mongodb\lib\bulk\common.js:1352:9)
at resultHandler (C:\Users\username\projectDirectory\node_modules\mongodb\lib\bulk\common.js:579:23)
at handler (C:\Users\username\projectDirectory\node_modules\mongodb\lib\core\sdam\topology.js:943:24)
at C:\Users\username\projectDirectory\node_modules\mongodb\lib\cmap\connection_pool.js:350:13
at handleOperationResult (C:\Users\username\projectDirectory\node_modules\mongodb\lib\core\sdam\server.js:558:5)
at MessageStream.messageHandler (C:\Users\username\projectDirectory\node_modules\mongodb\lib\cmap\connection.js:281:5)
at MessageStream.emit (events.js:321:20)
at processIncomingData (C:\Users\username\projectDirectory\node_modules\mongodb\lib\cmap\message_stream.js:144:12)
at MessageStream._write (C:\Users\username\projectDirectory\node_modules\mongodb\lib\cmap\message_stream.js:42:5)
at doWrite (_stream_writable.js:441:12)
Assuming collection name user_demo and having 2 fields only ( _id, name )
Create collection Schema Validator
db.createCollection("user_demo", {
validator: {
$jsonSchema: {
bsonType: "object",
title: "User Object Validation",
required: [ "_id","name"],
properties: {
_id: {
bsonType: "binData",
description: "Unique identifier,I am using it instead of objectId for portibility",
pattern: "^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
},
name: {
bsonType: "string",
description: "'name' must be a string and is required",
maxLength: 50,
minLength: 1
}
}
}
}
} )
Insert data in collection
a) If you already have a uuid4
db.user_demo.insertOne({_id: UUID("a5750db3-1616-45a4-bf92-6a44c3e67342"), name:"shiva"})
b) If you want random uuid4
db.user_demo.insertOne({_id: UUID(), name:"explore"})
Tested with mongo version 6.0.3

API to insert data to array of objects in mongoDB

I am trying to insert array of objects inside array of objects in my mongoDB schema. This is how i want my schema to appear.
const CourseSchema = mongoose.Schema({
categoryname: {
type: String,
required: "Course Category",
min: 3,
max: 100
},
coursename: {
type: String,
required: "Course Name",
min: 3,
max: 100
},
levels:
[
{
levelid: Number,
levelname: String,
chapter:
[
{
chapternumber: Number,
chaptername: String,
content: String //To be elaborated
}
]
}
]
});
My API which i have written looks like this:
exports.addcourse = (req, res) => {
let levels = [];
levels.push({
levelid: req.body.levelid,
levelname: req.body.levelname,
chapter: [
{
chapternumber: req.body.chapternumber,
chaptername: req.body.chaptername,
content: req.body.content
}
]
})
const newCourse = new Course({
coursename: req.body.coursename,
categoryname: req.body.categoryname,
levels: levels
});
newCourse.save(function (error) {
if (error) res.json({ message: 'could not add course because ' + error });
res.json({ newCourse: newCourse });
});
}
This works fine when i enter one level and one chapter, but gives an error when i enter multiple data.
I am giving input from postman 'x-www'form-urlencoded'.
please help.
The error i get when i add one more levelid and levelname in postman
{
"message": "could not add course because ValidationError: levels.0.levelid: Cast to Number failed for value \"[ '1', '2' ]\" at path \"levelid\", levels.0.levelname: Cast to String failed for value \"[ 'First Level', 'Second Level' ]\" at path \"levelname\""
}
The data i am trying to enter
In postman, when you are sending the same key levelid twice, it converts it to array containing both the values. Like in your case, in req.body.levelid, you will receive [1, 2]. But in your schema, levelid is expecting a number value. Also, you are reading it wrong in your API code. Instead of getting levelid and chapter seperately, you can get the levels array in request body with values levelid, levelname and chapter. Similarly, chapter can be an array of objects. req.body.levels will look like this:
[{
levelid: 1,
levelname: "level1",
chapter: [{
chapternumber: 1,
chaptername: "chapter1",
content: "chapter1-content"
}, {
chapternumber: 2,
chaptername: "chapter2",
content: "chapter2-content"
}]
}]
Try this in postman by giving input in raw

Mongoose update or insert many documents

I'm trying to use the latest version of mongoose to insert an array of objects, or update if a corresponding product id already exists. I can't for the life of me figure out the right method to use (bulkWrite, updateMany etc) and I can't can't seem to figure out the syntax without getting errors. For example, i'm trying
Product.update({}, products, { upsert : true, multi : true }, (err, docs) => console.log(docs))
this throws the error DeprecationWarning: collection.update is deprecated. Use updateOne, updateMany, or bulkWrite instead. MongoError: '$set' is empty. You must specify a field like so: {$set: {<field>: ...}
and using updateMany just gives me the $set error. I can't seem to find any examples of people using this method, just wondering if somebody can provide me with an example of how to properly use this.
To be clear, I generate an array of objects, then I want to insert them into my db, or update the entry if it already exists. The field I want to match on is called pid. Any tips or advice would be greatly appreciated!
EDIT:
My product schema
const productSchema = new mongoose.Schema({
title : String,
image : String,
price_was : Number,
price_current : {
dollars : String,
cents : String
},
price_save_percent : String,
price_save_dollars : String,
price_save_endtime : String,
retailer : String
})
const Product = mongoose.model('Product', productSchema)
an example of the products array being passed
[
{
title: 'SOME PRODUCT',
image: '',
price_was: '139.99',
price_current: { dollars: '123', cents: '.49' },
price_save_percent: '12%',
price_save_dollars: '16.50',
price_save_endtime: null,
pid: 'VB78237321',
url: ''
},
{ ... },
{ ... }
]
You basically need bulkWrite operation
The array you want to update with
const products = [
{
title: 'SOME PRODUCT',
image: '',
price_was: '139.99',
price_current: { dollars: '123', cents: '.49' },
price_save_percent: '12%',
price_save_dollars: '16.50',
price_save_endtime: null,
pid: 'VB78237321',
url: ''
}
]
The query for bulk update
Model.bulkWrite(
products.map((product) =>
({
updateOne: {
filter: { retailer : product.pid },
update: { $set: product },
upsert: true
}
})
)
)

How to load document with a custom _id by Mongoose?

Here is my schema definition:
var DocSchema = new mongoose.Schema({
_id: {
name: String,
path: String
},
label: String,
...
});
mongoose.model('Doc', DocSchema, 'doc_parse_utf8');
var Doc = mongoose.model('Doc');
And the documents have been inserted to mongodb by other program. Then I tried to query the document:
Doc.findOne({_id:{name:name,path:path}}, function(err, doc){
if (err && err_handler) {
err_handler(err);
} else if(callback) {
callback(doc);
}
});
But, a cast error will be reported:
{ message: 'Cast to ObjectId failed for value "[object Object]" at path "_id"',
name: 'CastError',
type: 'ObjectId',
value: { name: 'mobile', path: 'etc/' },
path: '_id' }
I have searched this problem on mongoose's document, google and statckoverflow.com, however, there's no any solution for me. Please help, thanks.
All you need to do is override the _id type by setting it to Mixed.
var UserSchema = new Schema({
_id: Schema.Types.Mixed,
name: String
});
This causes Mongoose to essentially ignore the details of the object.
Now, when you use find, it will work (nearly as expected).
I'd warn you that you'll need to be certain that the order of properties on the _id object you're using must be provided in the exact same order or the _ids will not be considered to be identical.
When I tried this for example:
var User = mongoose.model('User', UserSchema);
var testId = { name: 'wiredprairie', group: 'abc'};
var u = new User({_id: testId , name: 'aaron'});
u.save(function(err, results) {
User.find().where("_id", testId)
.exec(function(err, users) {
console.log(users.length);
});
});
The console output was 0.
I noticed that the actual data in MongoDB was stored differently than I thought it had been saved:
{
"_id" : {
"group" : "abc",
"name" : "wiredprairie"
},
"name" : "aaron",
"__v" : 0
}
As you can see, it's not name then group as I'd coded. (It was alphabetical, which made sense in retrospect).
So, instead, I did this:
var User = mongoose.model('User', UserSchema);
var testId = { name: 'wiredprairie', group: 'abc'};
var u = new User({_id: testId , name: 'aaron'});
u.save(function(err, results) {
User.find().where("_id", { group: 'abc', name: 'wiredprairie'})
.exec(function(err, users) {
console.log(users.length);
});
});
Then, the console output was 1.
I think you should re-design your schema. If the database is already on service, and can not change it now, you can temporary use this to solve the problem:
mongoose.connection.on('open', function () {
mongoose.connection.db.collection('doc_parse_utf8').find({
_id: {
name: 'mobile',
path: 'etc/'
}
}).toArray(function(err, docs) {
console.log(err || docs)
})
})
As I know if you choose different order of fields in object find method will not work because
_id: {
name: 'mobile',
path: 'etc/'
}
and
_id: {
path: 'etc/',
name: 'mobile'
}
are different keys.

Resources