You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

TypeScript:如何向Firestore提交嵌套对象并校验嵌套字段必填性?

解决Firestore嵌套对象的结构校验与提交问题

你的问题核心在于TypeScript接口仅在编译时提供类型提示,运行时不会自动校验传入数据的结构,而且当前代码既没有正确提取overview的字段,也没有对嵌套对象的必填项做校验,导致可以写入任意内容。下面分步骤解决这个问题:

1. 正确解构并嵌套存入Firestore

首先,你需要在解构时把overview也提取出来,并且明确指定它的必填字段,再一起放入projectObject中。这样即使请求里传了overview的额外字段,也会被过滤掉,只保留你需要的结构。修改后的addProject函数如下:

const addProject = async (req: Request, res: Response, next: NextFunction) => { 
  // 解构主对象和嵌套的overview
  const { projectName, projectType, country, city, overview } = req.body 
  // 进一步解构overview的必填字段,确保只保留需要的内容
  const { mainProject, projectId, m_tool_Id, businessUnitId } = overview || {};

  try { 
    const project = firestore.collection('projects').doc() 
    const projectObject = { 
      id: project.id, 
      projectName, 
      projectType, 
      country, 
      city, 
      overview: {
        mainProject,
        projectId,
        m_tool_Id,
        businessUnitId
      }
    } 
    await project.set(projectObject) // 别忘了加await,确保存储完成后再返回响应
    res.status(200).send({ 
      status: 'success', 
      message: 'project added successfully', 
      data: projectObject 
    }) 
  } catch (error) { 
    next(error); 
  } 
}

2. 添加运行时必填项校验

TypeScript的类型检查在编译后就失效了,所以必须手动校验所有必填字段是否存在、类型是否符合要求,否则可能会把undefined或错误类型的数据存入Firestore。

手动校验实现

const addProject = async (req: Request, res: Response, next: NextFunction) => { 
  const { projectName, projectType, country, city, overview } = req.body 

  // 校验主对象的必填字段
  const requiredMainFields = ['projectName', 'projectType', 'country', 'city'];
  const missingMainFields = requiredMainFields.filter(field => !req.body[field]);
  if (missingMainFields.length > 0) {
    return res.status(400).send({
      status: 'error',
      message: `缺少必填字段: ${missingMainFields.join(', ')}`
    });
  }

  // 校验嵌套overview的合法性
  if (!overview || typeof overview !== 'object') {
    return res.status(400).send({
      status: 'error',
      message: 'overview是必填项,且必须是对象类型'
    });
  }
  const requiredOverviewFields = ['mainProject', 'projectId', 'm_tool_Id', 'businessUnitId'];
  const missingOverviewFields = requiredOverviewFields.filter(field => !overview[field]);
  if (missingOverviewFields.length > 0) {
    return res.status(400).send({
      status: 'error',
      message: `overview中缺少必填字段: ${missingOverviewFields.join(', ')}`
    });
  }
  // 额外校验projectId的类型是否为数字
  if (typeof overview.projectId !== 'number') {
    return res.status(400).send({
      status: 'error',
      message: 'overview.projectId必须是数字类型'
    });
  }

  const { mainProject, projectId, m_tool_Id, businessUnitId } = overview;

  try { 
    const project = firestore.collection('projects').doc() 
    const projectObject = { 
      id: project.id, 
      projectName, 
      projectType, 
      country, 
      city, 
      overview: {
        mainProject,
        projectId,
        m_tool_Id,
        businessUnitId
      }
    } 
    await project.set(projectObject)
    res.status(200).send({ 
      status: 'success', 
      message: 'project added successfully', 
      data: projectObject 
    }) 
  } catch (error) { 
    next(error); 
  } 
}

使用Schema校验库简化(推荐)

如果你的项目有大量这类校验需求,手动写代码会很繁琐。可以用Zod这样的库,它能同时定义运行时Schema和生成TypeScript类型,一举两得:

首先安装Zod:

npm install zod

然后定义Schema并校验:

import { z } from 'zod';

// 定义overview的Schema
const OverviewSchema = z.object({
  mainProject: z.string(),
  projectId: z.number(),
  m_tool_Id: z.string(),
  businessUnitId: z.string()
});

// 定义Project的Schema
const ProjectSchema = z.object({
  projectName: z.string(),
  projectType: z.string(),
  country: z.string(),
  city: z.string(),
  overview: OverviewSchema
});

// 从Schema自动生成TypeScript类型
type Project = z.infer<typeof ProjectSchema>;

const addProject = async (req: Request, res: Response, next: NextFunction) => { 
  try {
    // 自动校验请求体是否符合Schema,不符合会抛出错误
    const validatedProject = ProjectSchema.parse(req.body);

    const project = firestore.collection('projects').doc() 
    const projectObject = { 
      id: project.id, 
      ...validatedProject // 直接展开校验后的对象,确保结构完全符合要求
    } 
    await project.set(projectObject)
    res.status(200).send({ 
      status: 'success', 
      message: 'project added successfully', 
      data: projectObject 
    }) 
  } catch (error) {
    if (error instanceof z.ZodError) {
      // 处理校验错误,返回详细的错误信息
      return res.status(400).send({
        status: 'error',
        message: '请求体格式错误',
        errors: error.errors.map(err => `${err.path.join('.')}: ${err.message}`)
      });
    }
    next(error);
  } 
}

关键要点总结

  • TypeScript接口≠运行时校验:接口只是编译时的类型约束,运行时必须自己做数据校验,否则无效数据会直接存入数据库。
  • 显式解构嵌套字段:只提取需要的字段,避免多余数据进入Firestore,保证数据结构整洁。
  • 必填项校验不可少:确保所有必填字段存在且类型正确,避免存入无效数据。
  • Schema库提升效率:对于复杂的嵌套结构,Zod等库能大幅减少重复代码,同时保持类型安全。

内容的提问来源于stack exchange,提问作者Paersik

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.28 12:24:06