我能否确定一个字符串是MongoDB的ObjectID?

147

我正在通过将字符串转换为BSON来查找MongoDB。在进行转换之前,有没有一种方法可以确定我拥有的字符串是否是Mongo的有效ObjectID?

这是我的当前findByID函数的CoffeeScript代码。它工作得很好,但如果我确定该字符串不是ID,我想通过其他属性进行查找。

db.collection "pages", (err, collection) ->
  collection.findOne
    _id: new BSON.ObjectID(id)
  , (err, item) ->
    if item
      res.send item
    else
      res.send 404
23个回答

239

我发现mongoose的ObjectId验证器可以验证有效的objectId,但我发现有几种情况下无效的id也被视为有效。(例如:任何12个字符长的字符串)

var ObjectId = require('mongoose').Types.ObjectId;
ObjectId.isValid('microsoft123'); //true
ObjectId.isValid('timtomtamted'); //true
ObjectId.isValid('551137c2f9e1fac808a5f572'); //true

对我有效的方法是将一个字符串转换为objectId,然后检查原始字符串是否与objectId的字符串值匹配。

new ObjectId('timtamtomted'); //616273656e6365576f726b73
new ObjectId('537eed02ed345b2e039652d2') //537eed02ed345b2e039652d2
这能够起作用是因为有效的id在转换为ObjectId时不会发生变化,但是一个获取到false有效值的字符串在转换为objectId时会发生变化。

这能够起作用是因为有效的id在转换为ObjectId时不会发生变化,但一个获得false有效值的字符串在转换为objectId时会发生改变。


理论上,您可以添加这两种方法来生成一个相当不错的ObjectID验证器,我今天会完成它。 - Anthony
3
这样可以吗?function checkObjectIdValid(id){ if(ObjectID.isValid(id)){ if(new ObjectID(id) === id){ return true } else { return false } } else { return false } } - Jackson Vaughan
7
实际上 @JacksonVaughan 的回答几乎是正确的。缺少一个 String() 将新的 ObjectID(id) 转换为字符串,因为我们要将其与另一个字符串进行比较。这里是完整正确的答案: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 } } - marcvander
3
让我为您将其转换为ES6语法:isObjectIdValid = id => ObjectId.isValid(id) ? String(new ObjectId(id) === id) ? true : false : false;,意思是检查传入的id值是否为有效的ObjectID格式,如果是则返回true,否则返回false。 - Rod911
3
现在已经存在标准函数(isValidObjectId),请检查我的答案-https://dev59.com/N2Up5IYBdhLWcg3w669C#61779949 - think-serious
显示剩余5条评论

112

✅ 内置解决方案 isValidObjectId() > Mongoose 5.7.12

如果您正在使用Mongoose,我们可以使用Mongoose内置的isValidObjectId来测试一个字符串是否为12字节或24个十六进制字符的字符串。

这就足以执行有效查询,而不会抛出任何无效的对象ID错误。

mongoose.isValidObjectId(string); /* will return true/false */

请注意!

isValidObjectId() 最常用于测试预期的 objectID,以避免出现 mongoose 抛出无效对象 ID 错误。

示例

if (mongoose.isValidObjectId("some 12 byte string")) {
     return collection.findOne({ _id: "some 12 byte string" })
     // returns null if no record found.
}
如果您不对预期的objectID进行有条件的测试,那么您将需要捕获错误。
try {
  return collection.findOne({ _id: "abc" }) 
  //this will throw error
} catch(error) {
  console.log('invalid _id error', error)
}

由于findOne({_id:null})findOne({_id:undefined})是完全有效的查询(不会抛出错误),因此isValidObjectId(undefined)isValidObjectId(null)将返回true。

注意事项2!

123456789012可能看起来不像bson字符串,但它完全是一个有效的ObjectID,因为以下查询不会抛出错误(如果未找到记录,则返回null)。

findOne({ _id: ObjectId('123456789012')}) // ✅ valid query

313233343536373839303132 看起来像一个包含24个字符的字符串(其实是十六进制值123456789012),但它也是一个有效的ObjectId,因为以下查询不会抛出错误。(如果未找到记录,则返回null)

findOne({ _id: ObjectId('313233343536373839303132')}) //  valid query
以下是无效的(比上面的例子少1个字符)
findOne({ _id: ObjectId('12345678901')}) //not 12 byte string
findOne({ _id: ObjectId('31323334353637383930313')}) //not 24 char hex

ObjectId的格式

ObjectId是小型、可能唯一、生成速度快和有序的。 ObjectId值的长度为12个字节,由以下组成:

  • 4字节时间戳值,表示ObjectId的创建时间,以Unix纪元后的秒数计量
  • 每个进程生成的5字节随机值。这个随机值是特定机器和进程唯一的。
  • 3字节递增计数器,初始化为一个随机值

由于上述随机值,ObjectId无法被推算。它只能显示为12个字节的字符串或24个字符的十六进制字符串。


Note: I have translated the content into Chinese as per the request.

2
认真地说,让我们投票支持这个事情。旧的被接受的答案在库不支持此功能时非常好用,但现在它已经支持了。绕过它是一种依赖于实现细节的黑客行为。 - lance.dolan
请注意,如果输入未定义,此函数将返回true。这可能不是您想要的结果。 - Mihai
是的和不是。undefined 可以被视为有效的 ObjectID,因为如果你执行 Model.find({ _id: undefined}),它不会抛出错误。而如果你执行 Model.find({ _id: 'abc'})(无效的 ObjectID),它将抛出 Cast to ObjectId failed 错误。 - Someone Special
这个字符串 - how-fit-am-i - 被返回为 true。有人能解释一下可能的原因吗? - trurohit
这是有效的,因为如果您使用ObjectId作为字段类型,则mongoose验证不会抛出错误。如果没有找到结果,它将简单地返回null。如果您使用较短/较长的字符串,则查询将抛出错误,需要通过try/catch块捕获。 - Someone Special
显示剩余7条评论

103
您可以使用正则表达式进行测试:
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    
}

1
哦,这也可能匹配非对象ID,最好的方法是基于规范构建一个验证器并使用正则表达式来匹配特定部分,或者尝试创建一个新的对象ID,并设置一个catch块以捕获是否可以执行它。 - Sammaye
2
@Sammaye 这是与BSON ObjectID构造函数相同的验证。你能给我一个不是ObjectID字符串的例子吗? - JohnnyHK
16
但这是一个有效的ObjectID,所以它应该匹配。 - JohnnyHK
请参考这里的评论,关于同一个问题的解答。 - gmajivu
2
可能是正确的方式,由mongoose官方建议 https://github.com/Automattic/mongoose/issues/1959#issuecomment-97457325 - Akarsh Satija
显示剩余4条评论

20

过去我曾使用本地节点MongoDB驱动程序来完成这个任务。isValid方法检查该值是否为有效的BSON ObjectId。在此处查看文档。

var ObjectID = require('mongodb').ObjectID;
console.log( ObjectID.isValid(12345) );

似乎不起作用,以上针对一个随机数返回true。 - Dan Ochiana
3
我认为最有可能的原因是应该是ObjectId而不是ObjectID。 :) - Ken Hoff
2
这应该是接受的答案,不需要mongoose。 - F.H.
我认为这个答案相当老了...在最新版本中我能找到的唯一东西是这个:https://mongodb.github.io/node-mongodb-native/5.3/classes/BSON.ObjectId.html#isValid,但我遇到了与@DanOchiana相同的问题,将一个数字传递给它会返回true - m.spyratos

6

如果字符串包含12个字母,那么mongoose.Types.ObjectId.isValid(string)函数始终返回True。

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

4

检查字符串是否是有效的Mongo ObjectId的最简单方法是使用mongodb模块。

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

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

Mongoose的流行程度真是疯狂。核心mongodb驱动程序更加强大,在新版本中支持POJO,而Mongoose只是一层低效的封装。 - Robert
是的。实际上,原生的Mongo查询函数比使用Mongoose函数要快得多。 - Pavneet Kaur

3
Mongoose 6.2.5引入了 mongoose.isObjectIdOrHexString() 方法,该方法仅在给定值为ObjectId实例或表示ObjectId的24个字符十六进制字符串时返回true,对于长度为12的数字、文档和字符串将返回false(与Mongoose 6中的mongoose.isValidObjectId()不同,它只是Mongoose 6中mongoose.Types.ObjectId.isValid()的包装函数)。

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

3
以下是一个函数,它既检查ObjectId的isValid方法,又检查new ObjectId(id)是否返回相同的值。之所以单独使用isValid不够,是由Andy Macleod在所选答案中很好地解释了原因。
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;
}

谢谢您的审查。我已更新描述。 - AliAvci
2
这样做不起作用,因为您正在对字符串和对象ID进行严格比较。请更新为双等号。 - Mattia Rasulo

2

我发现唯一的方法是创建一个新的ObjectId并将其值与要检查的值进行比较,如果输入等于输出,则该id有效:

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

2

这是我根据@andy-macleod的答案编写的代码。

它可以接受int、string或ObjectId,并在传递的值有效时返回有效的ObjectId,否则返回null:

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;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接