Can I determine if a string is a MongoDB ObjectID?
Solution 1
I found that the mongoose ObjectId validator works to validate valid objectIds but I found a few cases where invalid ids were considered valid. (eg: any 12 characters long string)
var ObjectId = require('mongoose').Types.ObjectId;
ObjectId.isValid('microsoft123'); //true
ObjectId.isValid('timtomtamted'); //true
ObjectId.isValid('551137c2f9e1fac808a5f572'); //true
What has been working for me is casting a string to an objectId and then checking that the original string matches the string value of the objectId.
new ObjectId('timtamtomted'); //616273656e6365576f726b73
new ObjectId('537eed02ed345b2e039652d2') //537eed02ed345b2e039652d2
This work because valid ids do not change when casted to an ObjectId but a string that gets a false valid will change when casted to an objectId.
Solution 2
You can use a regular expression to test for that:
CoffeeScript
if id.match /^[0-9a-fA-F]{24}$/
# it's an ObjectID
else
# nope
JavaScript
if (id.match(/^[0-9a-fA-F]{24}$/)) {
// it's an ObjectID
} else {
// nope
}
Solution 3
✅ Build In Solution isValidObjectId()
> Mongoose 5.7.12
If you are using Mongoose, we can test whether a String is of 12 bytes or a string of 24 hex characters by using mongoose build-in isValidObjectId.
mongoose.isValidObjectId(string); /* will return true/false */
🛑 DO NOTE!
isValidObjectId()
is most commonly used to test a expected objectID, in order to avoid mongoose throwing invalid object ID error.
Example
if (mongoose.isValidObjectId("some 12 byte string")) {
return collection.findOne({ _id: "some 12 byte string" })
// returns null if no record found.
}
If you do not conditionally test whether expected objectID is valid, you will need to catch the error.
try {
return collection.findOne({ _id: "abc" })
//this will throw error
} catch(error) {
console.log('invalid _id error', error)
}
Since findOne({ _id: null })
and findOne({ _id: undefined })
are completely valid queries (doesn't throw error), isValidObjectId(undefined)
and isValidObjectId(null)
will return true.
🛑 DO NOTE 2!
123456789012 may not appear to look like a bson string but it's completely a valid ObjectID because the following query does not throw error. (return null if no record found).
findOne({ _id: ObjectId('123456789012')}) // ✅ valid query
313233343536373839303132 may appear to look like a 24 character string (it's the hex value of 123456789012), but it's also a valid ObjectId because the following query does not throw error. (return null if no record found)
findOne({ _id: ObjectId('313233343536373839303132')}) // ✅ valid query
The following are invalid (1 string char less than above examples)
findOne({ _id: ObjectId('12345678901')}) // ❌ not 12 byte string
findOne({ _id: ObjectId('31323334353637383930313')}) // ❌ not 24 char hex
Format of ObjectId
ObjectIds are small, likely unique, fast to generate, and ordered. ObjectId values are 12 bytes in length, consisting of:
- a 4-byte timestamp value, representing the ObjectId's creation, measured in seconds since the Unix epoch
- a 5-byte random value generated once per process. This random value is unique to the machine and process.
- a 3-byte incrementing counter, initialized to a random value
Due to the above random value, ObjectId cannot be calculated. It can only appear to be a 12 byte string, or 24 character hex string.
Solution 4
I have used the native node mongodb driver to do this in the past. The isValid method checks that the value is a valid BSON ObjectId. See the documentation here.
var ObjectID = require('mongodb').ObjectID;
console.log( ObjectID.isValid(12345) );
Solution 5
mongoose.Types.ObjectId.isValid(string) always returns True if string contains 12 letters
let firstUserID = '5b360fdea392d731829ded18';
let secondUserID = 'aaaaaaaaaaaa';
console.log(mongoose.Types.ObjectId.isValid(firstUserID)); // true
console.log(mongoose.Types.ObjectId.isValid(secondUserID)); // true
let checkForValidMongoDbID = new RegExp("^[0-9a-fA-F]{24}$");
console.log(checkForValidMongoDbID.test(firstUserID)); // true
console.log(checkForValidMongoDbID.test(secondUserID)); // false
Related videos on Youtube
Comments
-
Will almost 2 years
I am doing MongoDB lookups by converting a string to BSON. Is there a way for me to determine if the string I have is a valid ObjectID for Mongo before doing the conversion?
Here is the coffeescript for my current findByID function. It works great, but I'd like to lookup by a different attribute if I determine the string is not an ID.
db.collection "pages", (err, collection) -> collection.findOne _id: new BSON.ObjectID(id) , (err, item) -> if item res.send item else res.send 404
-
Sammaye over 11 yearsHmm this could match non-objectIds as well, best way is to either build a validator based on the spec and regex it's specific parts or try to make a new objectid and house a catch block to catch if it can do it.
-
JohnnyHK over 11 years@Sammaye It's the same validation that's used by the BSON ObjectID constructor. Can you give me an example of a non-ObjectID string it would match?
-
Sammaye over 11 yearsWow, I didn't see that coming. Well any 24 character string that has numbers and letters in, i.e.
lol456712bbfghLLsdfr
-
JohnnyHK over 11 years@Sammaye The regex only matches alpha chars a-f and A-F so that string wouldn't match.
-
Sammaye over 11 yearsWell ok maybe I wrote that a little fast so:
456546576541232123adcdeA
would then match -
JohnnyHK over 11 years@Sammaye But that's a valid ObjectID, so it should match.
-
gmajivu over 10 yearssee comments here regarding the same
-
JohnnyHK over 9 yearsThis would evaluate to
true
for'funky string'
. Any string that's the right length and starts with a hex digit will satisfy it. -
Anthony about 8 yearsTheoretically you could add these two methods to generate a pretty damn good ObjectID validator, will get that done today.
-
Akarsh Satija over 7 yearsProbably the right way, officially suggested by mongoose github.com/Automattic/mongoose/issues/…
-
Dan Ochiana about 7 yearsdoesn't seem to work, above returns true for a random number.
-
Anthony over 6 yearsThis should be the accepted answer, this is what is officially suggested by the folks at mongoose.
-
Ken Hoff about 5 yearsI think that's most likely because it should be
ObjectId
, notObjectID
. :) -
Jackson Vaughan about 5 yearsSo, something like this?
function checkObjectIdValid(id){ if(ObjectID.isValid(id)){ if(new ObjectID(id) === id){ return true } else { return false } } else { return false } }
-
Andy Macleod about 5 yearsSomething like that would work, or string comparison using ObjetcId’s toString function.
-
Raz Buchnik almost 5 yearsBut if you got bad input which is not 12 length and contains special keys? You will get an error by the time you will do
ObjectId(INPUT)
. I think it better to wrap the validation using try and catch function. -
AliAvci over 4 yearsThank you for the review. I updated the description
-
marcvander over 4 yearsActually @JacksonVaughan answer is almost right. It was missing a String() to convert the new ObjectID(id) to a string since we are comparing it with another string. Here is the complete right answer:
const ObjectId = require('mongoose').Types.ObjectId; function isObjectIdValid(id) { if (ObjectId.isValid(id)) { if (String(new ObjectId(id)) === id) { return true } else { return false } } else { return false } }
-
Mattia Rasulo about 4 yearsThis won't work as you are doing a strict comparison between the string and object id. Please update to double equals.
-
Rod911 about 4 years@marcvander let me
es6
ify that for you:isObjectIdValid = id => ObjectId.isValid(id) ? String(new ObjectId(id) === id) ? true : false : false;
-
think-serious about 4 yearsStandard function exists now (isValidObjectId), check my answer here - stackoverflow.com/a/61779949/11896312
-
F.H. over 3 yearsthis should be accepted answer, no need for mongoose
-
lance.dolan over 3 yearsseriously, let's vote this thing up. The old accepted answer was great when the library provided no support for this, but now it does. To bypass it is a hack that depends on implementation details.
-
Avani Khabiya almost 3 yearsThis is accepting any random number of character length 12 as a valid object id. Is there any way to check only for object IDs with 24 characters that too in an accepted format i.e. alphanumeric? By accepted format this is what is meant like described here docs.mongodb.com/manual/reference/method/ObjectId
-
Someone Special almost 3 yearsnotice there is a random 5 byte value in the objectId. More often than not,
isValidObjectId
is more than sufficient since your queries will not be rejected by mongoose. If it's a invalid ObjectId, mongoose will throw error if your schema requires objectid. Checking for validObjectID is to reject the query before your mongoose throw the error. -
Mihai over 2 yearstake into account that this function returns true if the input is undefined. This may not be what you want.
-
Someone Special over 2 yearsYes and No.
undefined
can be treated as a valid ObjectID simply because if you doModel.find({ _id: undefined})
, it will not throw an error. While if you doModel.find({ _id: 'abc'})
(invalid ObjectID), it will throw a Cast to ObjectId failed error -
trurohit over 2 yearsThis string - how-fit-am-i - is being returned as true. Can anyone explain why that might be?
-
Someone Special over 2 yearsit’s is valid because error will not be thrown by your mongoose validation should you use ObjectId as the field type. It will simply return null if no results are found. If you were to use a shorter/longer string, your query will throw error which need to be caught by your try/catch block.
-
ImportError over 2 yearsSadly it validates
undefined
andnull
-
Someone Special over 2 yearsthis is correct behavior, because
findOne({ _id: undefined})
should not throw an error. undefined and null are valid values. You may refer to github.com/Automattic/mongoose/blob/… where it intentionally returns true on null -
ross-u over 2 yearsThis solution will evaluate to true the strings that begin with a hex digit and have a right length, such as
"comfort12345"
. -
Tushar Shahi over 2 yearsStill returns true for stuff like
microsoft123
andManajemenfs2
-
Someone Special over 2 yearsIt's true because it's a string of 12 characters and
findOne({_id: ObjectId('microsoft123')})
will return null (if record does not exist) instead of throwing error. -
Someone Special over 2 yearsadded explanation on why it's true in answer.
-
Robert about 2 yearsIt's nuts how popular Mongoose is. The core mongodb driver is more powerful and in newer versions with POJO support Mongoose is just a layer of inefficiency.
-
Pavneet Kaur about 2 yearsYes. In fact, native mongo query functions are way more faster than using mongoose functions.
-
superpupervlad about 2 yearsExplanation to why 12 characters can be correct id.
-
Uche Ozoemena almost 2 years@marcvander why use
String()
when the resulting ObjectId instance has atoString()
method?