Express.js中Mongoose子文档数组遍历访问问题
问题根因
你踩了三个Mongoose查询的基础错误,和子文档本身的定义没有关系:
Model.find()始终返回匹配查询条件的文档数组,哪怕按唯一_id查询只会匹配到1条数据,返回结果也是长度为1的数组,数组内的唯一元素才是你查到的Question文档本身。你直接把这个返回数组命名为answers,从变量定义开始就混淆了「匹配到的Question文档列表」和「Question内的answers子文档数组」两个完全不同的对象,后续的索引、长度读取自然全错。你看到控制台输出里有2条子文档,是因为这2条子文档存在于那唯一的Question文档的answers属性里,不是存在于find返回的顶层数组里,所以你读顶层数组的长度永远是1,访问索引1自然返回undefined。- Mongoose/MongoDB投影语法写错了。投影规则是传
1表示返回对应字段,传0表示排除对应字段,你写{"answers":2}虽然因为非0值会被判定为真值、也能返回answers字段,但属于无意义的错误写法。 - 后续调试的属性访问逻辑全错:你写的
answers.answers[0]、answers[0].length、answers[0].size都不符合JS对象访问规则——answers是find返回的文档数组,必须先取到数组内的Question文档(即answers[0]),再访问这个文档上的answers属性,才能拿到真正的子文档数组。你直接在Question文档对象上调length、size方法,当然拿不到正确结果。
修复方案
两种写法,更推荐第二种,逻辑更清晰不容易出错:
方案1:修正现有find逻辑
先拿到匹配的文档列表,取出目标文档后再读取子文档数组:
const { typeOf } = require('mathjs'); const Question = require('../models/Question') module.exports = async (req,res) =>{ console.log("You are inside displayAnswers controller!") const thisQuestionID = req.body.questionID; console.log("Display answers ID: " + thisQuestionID); if (thisQuestionID == 0) { console.log("Inside thisQuestionID == 0") res.redirect("index"); } else { // 修正投影语法,1表示返回对应字段 const questionList = await Question.find({"_id" : thisQuestionID}, {"answers": 1}); // 取出匹配到的唯一Question文档 const targetQuestion = questionList[0]; // 从文档上读取answers子文档数组 const answers = targetQuestion.answers; // 此时日志输出完全正常 console.log("Answers子文档数组: ", answers) console.log("第一条回答: ", answers[0]) console.log("回答总数: ", answers.length) res.render('displayAnswers', { thisQuestionID, answers }) } }
方案2:用findById替代find(推荐)
按_id查询单个文档时直接用Mongoose内置的findById方法,它会直接返回匹配到的单个文档对象,不需要处理数组下标,代码更简洁:
const { typeOf } = require('mathjs'); const Question = require('../models/Question') module.exports = async (req,res) =>{ console.log("You are inside displayAnswers controller!") const thisQuestionID = req.body.questionID; console.log("Display answers ID: " + thisQuestionID); if (thisQuestionID == 0) { console.log("Inside thisQuestionID == 0") res.redirect("index"); } else { // findById直接返回单个文档,第二个参数传字段字符串即可指定返回字段 const targetQuestion = await Question.findById(thisQuestionID, "answers"); const answers = targetQuestion.answers; console.log("Answers子文档数组: ", answers) console.log("第一条回答: ", answers[0]) console.log("回答总数: ", answers.length) res.render('displayAnswers', { thisQuestionID, answers }) } }
额外注意:你当前Schema里
datePosted的默认值写法有隐患,不要直接写default: new Date(),这样会把Schema加载时的时间固定为所有新文档的默认创建时间,正确写法是default: () => new Date(),用函数返回执行时的当前时间,避免所有文档创建时间一致的bug。
内容的提问来源于stack exchange,提问作者Rasputin
相关产品推荐
相关产品推荐

