Can I determine if a string is a MongoDB ObjectID?
Javascriptnode.jsMongodbCoffeescriptExpressJavascript Problem Overview
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
Javascript Solutions
Solution 1 - Javascript
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 - Javascript
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 - Javascript
isValidObjectId()
> Mongoose 5.7.12
✅ Build In Solution 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 - Javascript
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 - Javascript
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 - Javascript
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 - Javascript
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 - Javascript
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 - Javascript
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 - Javascript
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 11 - Javascript
@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 12 - Javascript
This approach may help somebody. It works with nodejs mongodb driver
if (ObjectId.isValid(stringId) && (ObjectId(stringId).toString() === stringId)){
// your operation
}
Solution 13 - Javascript
If you have the hex string you can use this:
ObjectId.isValid(ObjectId.createFromHexString(hexId));
Solution 14 - Javascript
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 - Javascript
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 - Javascript
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 - Javascript
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 - Javascript
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 - Javascript
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)