如何在Mongoose/MongoDB中返回单个嵌套任务对象
问题:根据任务ID从嵌套数组中获取单个任务对象
数据库文档结构
{ "_id": "62fa5aa25778ec97bc6ee231", "user": "62f0eb5ebebd0f236abcaf9d", "name": "Marketing Plan", "columns": [ { "name": "todo", "_id": "62fa5aa25778ec97bc6ee233", "tasks": [ { "title": "Task Four testing 2", "description": "This is task four", "subtasks": [ { "name": "wash dshes test", "completed": false, "_id": "62ff74bfe80b11ade2d34456" }, { "name": "do homework", "completed": false, "_id": "62ff74bfe80b11ade2d34457" } ], "_id": "62ff74bfe80b11ade2d34455" } ] }, { "name": "doing", "_id": "62fa5aa25778ec97bc6ee234", "tasks": [] }, { "name": "done", "_id": "62fa5aa25778ec97bc6ee235", "tasks": [] } ], "__v":0 }
预期需求
根据req.params.id返回对应的单个任务对象,例如ID为62ff74bfe80b11ade2d34455的任务,预期返回结果:
{ "title": "Task Four testing 2", "description": "This is task four", "subtasks": [ { "name": "wash dshes test", "completed": false, "_id": "62ff74bfe80b11ade2d34456" }, { "name": "do homework", "completed": false, "_id": "62ff74bfe80b11ade2d34457" } ], "_id": "62ff74bfe80b11ade2d34455" }
现有代码问题
使用aggregate框架编写的代码在Postman测试时无响应,代码如下:
const getTask = asyncHandler(async (req, res) => { const task = await Board.aggregate([ { $match: { "columns.tasks._id": req.params.id, }, }, { $project: { columns: { $first: { $filter: { input: "$columns.tasks", cond: { $eq: ["$$this._id", req.params.id], }, }, }, }, }, }, { $replaceRoot: { newRoot: "$columns", }, }, ]); });
问题分析与解决方案
问题点
- 未返回响应:代码中没有调用
res.json()或res.send()向客户端返回结果,导致请求一直处于挂起状态 - 嵌套数组处理错误:
$filter的input参数错误,$columns是数组,每个元素包含tasks数组,直接用$columns.tasks无法正确遍历嵌套结构 - 类型不匹配:
req.params.id是字符串类型,而数据库中_id是ObjectId类型,直接比较会匹配失败
修正后的代码方案一(使用aggregate展开数组)
const { ObjectId } = require('mongoose'); const getTask = asyncHandler(async (req, res) => { const taskId = new ObjectId(req.params.id); const result = await Board.aggregate([ // 匹配包含目标任务的Board文档 { $match: { "columns.tasks._id": taskId } }, // 展开columns数组,将每个column作为单独文档 { $unwind: "$columns" }, // 展开当前column下的tasks数组 { $unwind: "$columns.tasks" }, // 精准匹配目标任务 { $match: { "columns.tasks._id": taskId } }, // 将任务对象作为根文档返回 { $replaceRoot: { newRoot: "$columns.tasks" } }, // 只取第一个匹配结果(确保返回单个任务) { $limit: 1 } ]); if (result.length === 0) { return res.status(404).json({ message: "任务不存在" }); } // 返回目标任务 res.json(result[0]); });
修正后的代码方案二(使用findOne结合投影)
如果不想使用aggregate,也可以用findOne配合投影操作:
const { ObjectId } = require('mongoose'); const getTask = asyncHandler(async (req, res) => { const taskId = new ObjectId(req.params.id); // 查询包含目标任务的Board,并投影提取出目标任务 const board = await Board.findOne( { "columns.tasks._id": taskId }, { targetTask: { $reduce: { input: "$columns", initialValue: null, in: { $cond: [ // 判断当前column是否包含目标任务 { $ne: [{ $filter: { input: "$$this.tasks", cond: { $eq: ["$$this._id", taskId] } } , []] }, // 提取当前column中的目标任务 { $first: { $filter: { input: "$$this.tasks", cond: { $eq: ["$$this._id", taskId] } } }, // 保留之前找到的任务(如果有的话) "$$value" ] } } } } ); if (!board || !board.targetTask) { return res.status(404).json({ message: "任务不存在" }); } res.json(board.targetTask); });
内容的提问来源于stack exchange,提问作者Ope Afolabi
相关产品推荐
相关产品推荐

