Mongoose & MongoDB:向父Schema数组添加子Schema元素时触发CastError
评论添加失败的CastError问题排查与解决方案
问题背景
练习项目中,尝试给博客添加评论时触发CastError,后端包含Blog控制器、Blog模型、Comment模型及Comment控制器。
错误信息
CastError: Cast to [ObjectId] failed for value "[{"commentText":"Cool","authorName":"David"}]" (type string) at path "comments.0" at model.Query.exec (/Users/david/Documents/Coding/codingChallenge/react-blog-server/node_modules/mongoose/lib/query.js:4498:21) at model.Query.Query.then (/Users/david/Documents/Coding/codingChallenge/react-blog-server/node_modules/mongoose/lib/query.js:4592:15) at processTicksAndRejections (internal/process/task_queues.js:93:5)
相关代码
初始控制器代码
import { Request, Response } from 'express'; import BaseController from '../baseController'; import { BlogPostSchema } from '../../repository/blogPost'; export default class BlogPostController extends BaseController { constructor() { super(); } public async test(req: Request, res: Response) { res.status(200).send('test worked'); } public async addComment(req: Request, res: Response) { const newComment = req.body; const updatedBlogPost = await BlogPostSchema.findByIdAndUpdate( { _id: req.params.id }, { $push: { comments: newComment } }, { new: true } ); res.status(200).send(updatedBlogPost); } }
BlogPost Schema
import { createSchema, ExtractDoc, ExtractProps, Type, typedModel } from 'ts-mongoose'; import { commentSchema } from './comment'; export const blogPostSchema = createSchema( { authorName: Type.string({ trim: true }), blogText: Type.string({ trim: true }), category: Type.string({ default: '', trim: true, enum: ['gaming', 'lifestyle', 'cooking', 'baking'] }), comments: Type.array().of( Type.ref(Type.objectId()).to('comment', commentSchema) ) }, { versionKey: false, timestamps: true } ); export const BlogPostSchema = typedModel('blogPost', blogPostSchema); export type BlogPost = ExtractProps<typeof blogPostSchema>; export type BlogPostDoc = ExtractDoc<typeof blogPostSchema>;
Comment Schema
import { createSchema, ExtractDoc, ExtractProps, Type, typedModel } from 'ts-mongoose'; export const commentSchema = createSchema( { commentText: Type.string({ trim: true }), authorName: Type.string({ trim: true }) }, { versionKey: false, timestamps: true } ); export const CommentModel = typedModel('comment', commentSchema); export type Comment = ExtractProps<typeof commentSchema>; export type CommentDoc = ExtractDoc<typeof commentSchema>;
尝试的另一种失败写法
public async addComment(req: Request, res: Response) { const newComment = await BlogPostSchema.create(req.body); const updatedBlogPost = await BlogPostSchema.findByIdAndUpdate( { _id: req.params.id }, { $push: { comments: newComment } }, { new: true } ); res.status(200).send(updatedBlogPost); }
问题原因
- Schema定义与传入值不匹配:BlogPost的
comments字段被定义为引用Comment文档的ObjectId数组,但你直接传入了评论对象(或字符串化的对象),Mongoose无法将其转换为合法的ObjectId,因此抛出CastError。 - 错误的文档创建方式:第二次尝试中用
BlogPostSchema.create(req.body)创建评论,这是错误的,应该使用CommentModel.create来创建Comment文档。
解决方案
方案1:使用引用关系(推荐,适合需要单独操作评论的场景)
先创建Comment文档,再将其ObjectId推入BlogPost的comments数组:
// 记得导入CommentModel import { CommentModel } from '../../repository/comment'; public async addComment(req: Request, res: Response) { try { // 1. 创建评论文档 const newComment = await CommentModel.create(req.body); // 2. 将评论的_id推入博客的comments数组,populate返回完整评论内容 const updatedBlogPost = await BlogPostSchema.findByIdAndUpdate( req.params.id, { $push: { comments: newComment._id } }, { new: true, populate: 'comments' } ); res.status(200).send(updatedBlogPost); } catch (err) { res.status(500).send(err); } }
方案2:使用嵌入式文档(适合评论无需单独操作的场景)
修改BlogPost Schema中的comments字段,将其定义为嵌入式文档数组,而非引用:
// 修改blogPostSchema中的comments字段 comments: Type.array().of(commentSchema)
此时可直接推送评论对象到数组:
public async addComment(req: Request, res: Response) { try { const updatedBlogPost = await BlogPostSchema.findByIdAndUpdate( req.params.id, { $push: { comments: req.body } }, { new: true } ); res.status(200).send(updatedBlogPost); } catch (err) { res.status(500).send(err); } }
下一步排查方向
- 打印
req.body和typeof req.body,确认请求体是否被正确解析为JSON对象(而非字符串) - 检查数据库中已存在的BlogPost文档,验证comments字段的类型是否与Schema定义一致
- 单独测试
CommentModel.create(req.body),确认能成功创建评论文档并返回合法的ObjectId
内容的提问来源于stack exchange,提问作者David Reke
相关产品推荐
相关产品推荐

