Can I determine if a string is a MongoDB ObjectID?

99,755

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
Share:
99,755

Related videos on Youtube

Will
Author by

Will

I'm a professional full stack Javascript developer.

Updated on July 08, 2022

Comments

  • Will
    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
    Sammaye over 11 years
    Hmm 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
    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
    Sammaye over 11 years
    Wow, I didn't see that coming. Well any 24 character string that has numbers and letters in, i.e. lol456712bbfghLLsdfr
  • JohnnyHK
    JohnnyHK over 11 years
    @Sammaye The regex only matches alpha chars a-f and A-F so that string wouldn't match.
  • Sammaye
    Sammaye over 11 years
    Well ok maybe I wrote that a little fast so: 456546576541232123adcdeA would then match
  • JohnnyHK
    JohnnyHK over 11 years
    @Sammaye But that's a valid ObjectID, so it should match.
  • gmajivu
    gmajivu over 10 years
    see comments here regarding the same
  • JohnnyHK
    JohnnyHK over 9 years
    This would evaluate to true for 'funky string'. Any string that's the right length and starts with a hex digit will satisfy it.
  • Anthony
    Anthony about 8 years
    Theoretically you could add these two methods to generate a pretty damn good ObjectID validator, will get that done today.
  • Akarsh Satija
    Akarsh Satija over 7 years
    Probably the right way, officially suggested by mongoose github.com/Automattic/mongoose/issues/…
  • Dan Ochiana
    Dan Ochiana about 7 years
    doesn't seem to work, above returns true for a random number.
  • Anthony
    Anthony over 6 years
    This should be the accepted answer, this is what is officially suggested by the folks at mongoose.
  • Ken Hoff
    Ken Hoff about 5 years
    I think that's most likely because it should be ObjectId, not ObjectID. :)
  • Jackson Vaughan
    Jackson Vaughan about 5 years
    So, 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
    Andy Macleod about 5 years
    Something like that would work, or string comparison using ObjetcId’s toString function.
  • Raz Buchnik
    Raz Buchnik almost 5 years
    But 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
    AliAvci over 4 years
    Thank you for the review. I updated the description
  • marcvander
    marcvander over 4 years
    Actually @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
    Mattia Rasulo about 4 years
    This won't work as you are doing a strict comparison between the string and object id. Please update to double equals.
  • Rod911
    Rod911 about 4 years
    @marcvander let me es6ify that for you: isObjectIdValid = id => ObjectId.isValid(id) ? String(new ObjectId(id) === id) ? true : false : false;
  • think-serious
    think-serious about 4 years
    Standard function exists now (isValidObjectId), check my answer here - stackoverflow.com/a/61779949/11896312
  • F.H.
    F.H. over 3 years
    this should be accepted answer, no need for mongoose
  • lance.dolan
    lance.dolan over 3 years
    seriously, 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
    Avani Khabiya almost 3 years
    This 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
    Someone Special almost 3 years
    notice 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
    Mihai over 2 years
    take into account that this function returns true if the input is undefined. This may not be what you want.
  • Someone Special
    Someone Special over 2 years
    Yes and No. undefined can be treated as a valid ObjectID simply because if you do Model.find({ _id: undefined}), it will not throw an error. While if you do Model.find({ _id: 'abc'}) (invalid ObjectID), it will throw a Cast to ObjectId failed error
  • trurohit
    trurohit over 2 years
    This string - how-fit-am-i - is being returned as true. Can anyone explain why that might be?
  • Someone Special
    Someone Special over 2 years
    it’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
    ImportError over 2 years
    Sadly it validates undefined and null
  • Someone Special
    Someone Special over 2 years
    this 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
    ross-u over 2 years
    This solution will evaluate to true the strings that begin with a hex digit and have a right length, such as "comfort12345".
  • Tushar Shahi
    Tushar Shahi over 2 years
    Still returns true for stuff like microsoft123 and Manajemenfs2
  • Someone Special
    Someone Special over 2 years
    It'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
    Someone Special over 2 years
    added explanation on why it's true in answer.
  • Robert
    Robert about 2 years
    It'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
    Pavneet Kaur about 2 years
    Yes. In fact, native mongo query functions are way more faster than using mongoose functions.
  • superpupervlad
    superpupervlad about 2 years
  • Uche Ozoemena
    Uche Ozoemena almost 2 years
    @marcvander why use String() when the resulting ObjectId instance has a toString() method?