javascript

node.js

mongodb

express

coffeescript

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

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

Solution 6

Here is some code I have written based on @andy-macleod's answer.

It can take either an int or string or ObjectId and returns a valid ObjectId if the passed value is valid or null if it is invalid:

var ObjectId= require('mongoose').Types.ObjectId;

function toObjectId(id) {

    var stringId = id.toString().toLowerCase();

    if (!ObjectId.isValid(stringId)) {
        return null;
    }

    var result = new ObjectId(stringId);
    if (result.toString() != stringId) {
        return null;
    }

    return result;
}

Solution 7

The simplest way to check if the string is a valid Mongo ObjectId is using mongodb module.

const ObjectID = require('mongodb').ObjectID;

if(ObjectID.isValid(777777777777777)){
   console.log("Valid ObjectID")
}

Solution 8

The only way i found is to create a new ObjectId with the value i want to check, if the input is equal to the output, the id is valid :

function validate(id) {
    var valid = false;
    try
    {
        if(id == new mongoose.Types.ObjectId(""+id))
           valid = true;

    }
    catch(e)
    {
       valid = false;
    }
    return valid;
}

> validate(null)
false
> validate(20)
false
> validate("abcdef")
false
> validate("5ad72b594c897c7c38b2bf71")
true

Solution 9

The easiest way is basically wrap your ObjectId method in a try and catch service. Then you are using this service to handle Objecet Id's, instead of using the method directly:

var ObjectId = REQUIRE OR IMPORT ...

// service
function oid(str) {
 try {   
   return ObjectId(str);
 } catch(err) {
   return false;
 }
}

// usage
if (oid(USER_INPUT)) {
  // continue
} else {
  // throw error
}

You can also send null or empty props to get a new generated ID.

Solution 10

@ross-u answer is just amazing.

I have chained the methods to do a full validation inline:

documentId = id && isValid(id) && new ObjectId(id) == id ? id : null

Note the double equal sign, which is VERY important as new ObjectId() does not return a string and strict comparison will return false when compared against a normal string (which I had coming in my logic).

The methods have been destructured from the mongoose object exposed by the require:

const {
  Types: {
    ObjectId: { isValid },
    ObjectId
  }
} = require("mongoose");

Solution 11

Below is a function that both checks with the ObjectId isValid method and whether or not new ObjectId(id) returns the same value. The reason for isValid not being enough alone is described very well by Andy Macleod in the chosen answer.

const ObjectId = require('mongoose').Types.ObjectId;

/**
 * True if provided object ID valid
 * @param {string} id 
 */
function isObjectIdValid(id){ 
  return ObjectId.isValid(id) && new ObjectId(id) == id;
}

Solution 12

This approach may help somebody. It works with nodejs mongodb driver

if (ObjectId.isValid(stringId) && (ObjectId(stringId).toString() === stringId)){
  // your operation
}

Solution 13

If you have the hex string you can use this:

ObjectId.isValid(ObjectId.createFromHexString(hexId));

Solution 14

It took me a while to get a valid solution as the one proposed by @Andy Macleod of comparing objectId value with its own string was crashing the Express.js server on:

var view_task_id_temp=new mongodb.ObjectID("invalid_id_string"); //this crashed

I just used a simple try catch to solve this.

var mongodb = require('mongodb');
var id_error=false;
try{
    var x=new mongodb.ObjectID("57d9a8b310b45a383a74df93");
    console.log("x="+JSON.stringify(x));
}catch(err){
    console.log("error="+err);
    id_error=true;
}

if(id_error==false){
   // Do stuff here
}

Solution 15

For mongoose , Use isValid() function to check if objectId is valid or not

Example :

var ObjectId = mongoose.Types.ObjectId;
if(ObjectId.isValid(req.params.documentId)){
   console.log('Object id is valid'); 
}else{
   console.log('Invalid Object id');
}

Solution 16

Adding to the accepted answer of Andy Macleod, I created a helper function that can be used to check both strings and ObjectId's.

Implentation:

var ObjectId = require("mongoose").Types.ObjectId;


function isValidObjectId(value) {
  // If value is an ObjectId cast it to a string to allow
  // passing string or ObjectId as an argument.
  var valueString = typeof value === "string" ? value : String(value); 
  
  // Cast the string to ObjectId
  var idInstance = new ObjectId(valueString); 

  return String(idInstance) === valueString;
}

Explanation:

In the accepted answer Andy Macleod said:

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.


With Invalid ObjectId string

Casting an invalid string (like "microsoft") to ObjectId, gives a completely different value:

        "microsoft"
            
String( new ObjectId("microsoft")  );
            
"6d6963726f736f6674313233"
            
"microsoft" === "6d6963726f736f6674313233" // false

With Valid ObjectId string

Casting a valid string like ("6d6963726f736f6674313233") to ObjectId, gives the same value:

"6d6963726f736f6674313233"
            
String( new ObjectId("6d6963726f736f6674313233") )
            
"6d6963726f736f6674313233"     
            
"6d6963726f736f6674313233" === "6d6963726f736f6674313233"  // true

Solution 17

https://mongodb.github.io/node-mongodb-native/api-bson-generated/objectid.html#objectid-isvalid

From MongoDB documentation:

Constructor documentation:

ObjectID()
Constructor
Create a new ObjectID instance

class ObjectID()
    Arguments:  
        id (string)  Can be a 24 byte hex string, 12 byte binary string or a Number.
    Returns:    
        object instance of ObjectID.

ObjectId.isValid() documentation:

Checks if a value is a valid bson ObjectId

ObjectID.isValid()
    Returns:    boolean return true if the value is a valid bson ObjectId, return false otherwise.

So, isValid(...) will return true if argument is a valid BSON ObjectId object, and the constructor ObjectId(...) only accepts a valid BSON ObjectId object argument (Basically, isValid(...) will return false if an exception will be thrown by constructor).

Knowing this. We can first check if argument is a valid ObjectId, and then create an ObjectId with that value and compare HEX strings.

const objectId = 'str_object_id';
const isValid = ObjectId.isValid(objectId) && new ObjectId(objectId).toHexString() == objectId;

If passed objectId is not a valid ObjectId HEX string (it has not been converted by constructor), this method will return false.

Solution 18

Mongoose 6.2.5 introduces mongoose.isObjectIdOrHexString() which returns true only if the given value is an ObjectId instance or a 24 character hex string representing an ObjectId, and will return false for numbers, documents, and strings of length 12 (unlike mongoose.isValidObjectId() which is just a wrapper for mongoose.Types.ObjectId.isValid() in Mongoose 6)

Mongoose.prototype.isObjectIdOrHexString()

Parameters

  • v «Any»

Returns true if the given value is a Mongoose ObjectId (using instanceof) or if the given value is a 24 character hex string, which is the most commonly used string representation of an ObjectId.

This function is similar to isValidObjectId(), but considerably more strict, because isValidObjectId() will return true for any value that Mongoose can convert to an ObjectId. That includes Mongoose documents, any string of length 12, and any number. isObjectIdOrHexString() returns true only for ObjectId instances or 24 character hex strings, and will return false for numbers, documents, and strings of length 12.

Example:

mongoose.isObjectIdOrHexString(new mongoose.Types.ObjectId()); // true
mongoose.isObjectIdOrHexString('62261a65d66c6be0a63c051f'); // true

mongoose.isObjectIdOrHexString('0123456789ab'); // false
mongoose.isObjectIdOrHexString(6); // false
mongoose.isObjectIdOrHexString(new User({ name: 'test' })); // false
mongoose.isObjectIdOrHexString({ test: 42 }); // false

Solution 19

If you don't want to use a function exported by a mongo db driver or ORM, you can do some simple objectId validation using this RegExp:

/[0-9a-f]{24}/i

Check this thread for more information.

Solution 20

Warning: isValid will return true for arbitrary 12/24 length strings beginning with a valid hex digit. Currently I think this is a better check:

((thing.length === 24 || thing.length === 12) && isNaN(parseInt(thing,16)) !== true)