Mongoose .lean() 方法的相等性测试失败
Posted
技术标签:
【中文标题】Mongoose .lean() 方法的相等性测试失败【英文标题】:Equality Test Fails with Mongoose .lean() Method 【发布时间】:2021-05-27 03:09:45 【问题描述】:我正在尝试将 req.user._id
与从 MongoDB 查询返回的 ObjectId 数组进行比较。但是所有.includes()
、严格和松散的相等检查都失败了。
这是我的控制器中的逻辑(为简单起见被截断):
// Get the ID of the document from the request
const someDocId = req.body.id;
// Perform the search with projection
const result = await Some_DB.findById(someDocId,adminIds:1, _id:0).lean();
/*
The structure of the query result is as the following:
adminIds: [ 5f77ba7d1a0fba8f5e811e76, 6035f2e7174d4961808944d1 ],
And req.user._id is equal to 6035f2e7174d4961808944d1
*/
// When I do
console.log(result.adminIds[1] === req.user._id);
console.log(result.adminIds[1] == req.user._id);
console.log(result.adminIds.includes(req.user._id))
// I also tried
const ObjectId, = require('mongoose').Types
console.log(result.adminIds[1] === ObjectId(req.user._id));
console.log(result.adminIds[1] == ObjectId(req.user._id));
console.log(result.adminIds.includes(ObjectId(req.user._id)))
// The result is always false
// Additional Info:
(the results below are the same with or without .lean()
const ObjectId, = require('mongoose').Types
const a = sphereInfo.adminIds[1];
const b = req.user._id;
console.log(a instanceof ObjectId); // => true
console.log(b instanceof ObjectId); // => true
console.log(typeof(result.adminIds[1])); // => object
console.log(typeof(req.user._id)); // => object
console.log(result.adminIds[1]);
// 6035f2e7174d4961808944d1 (note:there is no single quote around)
console.log(req.user._id);
// 6035f2e7174d4961808944d1 (note:there is no single quote around)
const a = Object.values(sphereInfo.adminIds[1]);
const b = Object.values(req.user._id);
console.log(a); // => [ 'ObjectID', <Buffer 60 35 f2 e7 17 4d 49 61 80 89 44 d1> ]
console.log(b); // => [ 'ObjectID', <Buffer 60 35 f2 e7 17 4d 49 61 80 89 44 d1> ]
const a = Object.getOwnPropertyNames(sphereInfo.adminIds[1]);
const b = Object.getOwnPropertyNames(req.user._id);
console.log(a); // => [ '_bsontype', 'id' ]
console.log(b); // => [ '_bsontype', 'id' ]
console.log(Object.entries(sphereInfo.adminIds[1]));
console.log(Object.entries(req.user._id));
/*
Result:
[
[ '_bsontype', 'ObjectID' ],
[ 'id', <Buffer 60 35 f2 e7 17 4d 49 61 80 89 44 d1> ]
]
[
[ '_bsontype', 'ObjectID' ],
[ 'id', <Buffer 60 35 f2 e7 17 4d 49 61 80 89 44 d1> ]
]
*/
console.log(JSON.stringify(sphereInfo.adminIds[1]));
console.log(JSON.stringify(req.user._id));
/*
Result:
"6035f2e7174d4961808944d1"
"6035f2e7174d4961808944d1"
*/
// After removing .lean()
console.log(result.adminIds[1] === req.user._id); // => false
console.log(result.adminIds[1] == req.user._id); // => false
console.log(result.adminIds.includes(req.user._id)) // => true
架构(为简单起见被截断):
// Dependencies
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
// Create Schema
const SomeSchema = new Schema(
adminIds: [
type: Schema.Types.ObjectId,
ref: 'user',
],
,
collection: 'SomeCollection',
timestamps: true,
);
module.exports = SomeSchema, ;
无论我怎么看,它们都是完全相同的。但是,所有 .includes()
、严格和松散的相等检查都返回 false。更让我困惑的是,无论有没有.lean()
,对象结构都是一样的
如果我从查询中删除 .lean()
,所有检查仍然失败,只有 .inclides()
返回 true。
根据猫鼬Doc.:
精益选项告诉 Mongoose 跳过对结果文档进行水合。这使得查询更快,内存占用更少,但结果文档是普通的旧 javascript 对象 (POJO),而不是 Mongoose 文档。默认情况下,Mongoose 查询返回 Mongoose Document 类的一个实例。文档比普通 JavaScript 对象重得多,因为它们有很多用于更改跟踪的内部状态。启用精益选项会告诉 Mongoose 跳过实例化完整的 Mongoose 文档,而只为您提供 POJO。
文档还提到启用精益的缺点是精益文档没有:
更改跟踪 铸造和验证 getter 和 setter 虚拟 保存() 我无法看到.lean()
可以影响相等性检查以及.includes()
方法。
所以最后的问题是有人可以向我解释.lean()
是如何导致相等检查失败但.includes
通过的吗?
注意:我不是在问如何比较它们以便检查通过,而是.lean()
如何导致检查失败但 `.includes() 通过。
【问题讨论】:
如果您打印两个值,它们是否相同?也打印它们的类型。 @MinusFour 我做到了。console.log(typeof(result.adminIds[1])); // => object console.log(typeof(req.user._id)); // => object
那么您在帖子中的结果结构具有误导性。这些很可能都是不同的对象,因此相等检查失败的原因。对象的结构是什么?
如果您在控制台上获得该输出,那么lean
的文档是在撒谎,或者至少我不会认为它们是 POJO。对两个对象执行Object.entries
或JSON.stringify
。这将使您了解有关对象结构的更多信息。
@MinusFour,我已经用信息编辑了问题。你指定了。
【参考方案1】:
区别很微妙,但确实存在。使用.lean()
,您的查询最终会使用result.adminIds
中的数组解析;需要注意的是,这个数组的每个元素都是 ObjectId - 正如@codemonkey 正确提到的那样,它仍然是 ObjectId - 一个对象。当您尝试使用 includes
在该数组中查找特定的 ObjectId 时,由于引用不相等,搜索只会失败。
然而,当查询按原样执行时,没有应用.lean()
,result.adminIds
不再只是一个数组——它是MongooseArray,它有很多数组方法基本上被覆盖。这就是Mongoose.prototype.indexOf(用于MongooseArray.prototype.includes)的样子:
indexOf(obj, fromIndex)
if (obj instanceof ObjectId)
obj = obj.toString();
fromIndex = fromIndex == null ? 0 : fromIndex;
const len = this.length;
for (let i = fromIndex; i < len; ++i)
if (obj == this[i])
return i;
return -1;
如您所见,这里的第一步是将第一个参数(如果是 ObjectId)转换为字符串,基本上消除了引用检查。这就是为什么includes
既可以用于直接字符串比较,也可以用于 ObjectId 的原因。
但最有趣的部分如下:因为在查找中使用==
(而不是===
),存储在该数组中的ObjectId 值在与字符串比较时被转换为基元。这就是为什么即使直接比较为您提供false
,includes()
实际上为您提供了方便(但令人困惑,真实)的解决方法。
【讨论】:
这清楚了很多事情。但是根据 MDN Web Doc,“includes() 使用 sameValueZero 算法来确定是否找到给定的元素”,如果比较对象,它将始终返回 false。但数组也是对象。 MongooseArray 也覆盖了includes()
方法。
那么.includes()
的猫鼬版本也使用.indexOf()
的猫鼬版本吗?正在通过您嵌入在 MongooseArray 中的链接检查 mongoose 文档。好像他们是独立的。
@AvivLo const ret = this.indexOf(obj, fromIndex);
表示使用 MongooseArray
的 indexOf 版本,而不是数组的版本。
谢谢。你让我今天一整天都感觉很好。您答案的最后一点确实带来了普通人不会费心去挖掘的东西。非常感谢您的努力。【参考方案2】:
lean()
将返回 POJO,但 ObjectId 值仍将是 Object 类型。
您自己的测试结果证实:
console.log(typeof(result.adminIds[1])); // => object
console.log(typeof(req.user._id)); // => object
因此,要比较这两个值,您只需要像这样比较:
console.log(result.adminIds[1].toString() === req.user._id.toString());
至少我一直在做的,而且它总是有效的。
【讨论】:
感谢您的回答。也许我应该让问题更清楚,我不是在问如何比较它们以便我可以通过检查,而是 .lean 如何导致检查失败。【参考方案3】:我不完全确定为什么文档坚持它们是 POJO,但您得到的结果确实与 MongoDB ObjectID 对象匹配。也许他们说它们是 POJO,因为它们不是由相关的构造函数构造的,它们只是传递给新对象的属性。
如果您想比较您在控制台上看到的十六进制值,您需要比较 str
属性。
console.log(result.adminIds[1].str === req.user._id.str);
否则,您正在比较两个不同的对象(这总是错误的)。
这是检查真假的唯一方法:
console.log(result.adminIds[1] === req.user._id);
如果它们都是同一个对象或者它们都是同一个字符串。
在我看来
我不会将扩展 valueOf
和/或 toString
的对象称为 POJO,因为它本质上是扩展 JS 对象的某些功能。因此,不再简单了。
【讨论】:
谢谢。我知道在 javascript 中比较对象将始终返回 false,除非它们都引用相同的实例或相同的内存位置。但是即使在没有.lean()
的情况下执行查询,MongoDB 仍然会返回对象,但相等性检查仍然通过。
是的,但是就像文档中所说的,这些对象为了方便而被大量扩展。所以他们通过getter和setter增加了很多“魔法”。尝试使用lean
和不使用lean
检查两个对象属性的类型。如果检查通过,您很可能会看到不同的类型,或者 getter
足够聪明,可以返回现有的 objectid。
编辑:在移除 .lean()
后,并非所有相等性检查都通过,只有 .includes()
会通过。
includes
?
删除.lean()
后的那个以上是关于Mongoose .lean() 方法的相等性测试失败的主要内容,如果未能解决你的问题,请参考以下文章