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])); // =&gt; object console.log(typeof(req.user._id)); // =&gt; object 那么您在帖子中的结果结构具有误导性。这些很可能都是不同的对象,因此相等检查失败的原因。对象的结构是什么? 如果您在控制台上获得该输出,那么lean 的文档是在撒谎,或者至少我不会认为它们是 POJO。对两个对象执行Object.entriesJSON.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 值在与字符串比较时被转换为基元。这就是为什么即使直接比较为您提供falseincludes() 实际上为您提供了方便(但令人困惑,真实)的解决方法。

【讨论】:

这清楚了很多事情。但是根据 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() 方法的相等性测试失败的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 xcttest 测试对象相等性?

如何在 JSF 中测试枚举相等性?

如何使用 OffsetDateTime 属性测试数据类的相等性?

chai 测试数组相等性没有按预期工作

在 Realm 中测试相等性

用 Jest 测试匿名函数相等性