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

如何在Zod中定义带默认值可选属性 解决类型与运行时不匹配问题

Zod带默认值可选字段类型与运行时不匹配问题

问题复现

使用Zod定义包含默认值的可选字段Schema时,出现类型推断与运行时逻辑矛盾:

  • 预期:字段为可选属性,未传入或传入undefined时自动应用预设默认值
  • 实际:要么运行时默认值生效但TS类型推断字段为必填,要么类型标记为可选但运行时默认值不生效

复现代码如下:

import { Timestamp } from 'firebase/firestore';
import { z } from 'zod';

export const someSchema = z.object({
  id: z.string(),
  timestamp: z.instanceof(Timestamp),
  type: z.enum(['fever', 'constipation']),
  notes: z.string().optional().default(''),
});

export const someInput = someSchema
  .omit({ id: true })
  .merge(
    z.object({
      timestamp: z
        .date()
        .optional()
        .default(() => new Date()),
    }),
  )
  .partial({
    notes: true,
  });

export const schemaArray = z.array(someSchema);

export type Schema = z.infer<typeof someSchema>;
export type SchemaInput = z.infer<typeof someInput>; // 期望timestamp为可选,实际推断为必填


function a({ type, timestamp, notes}: SchemaInput){
  someInput.parse({
  type, timestamp, notes
  })
}

a({type: 'fever'}) // TS报错:timestamp为必填字段

问题根源

  • 类型用错:z.infer获取的是Schema调用parse后的输出类型,带默认值的字段在输出时一定存在值,因此会被推断为必填;而传给parse的入参类型需要用z.input获取,这是很多人容易混淆的点。
  • 冗余调用:.default()本身已经内置了「入参为undefined或缺省时自动填充默认值」的逻辑,不需要提前链式调用.optional(),多余的.optional()会干扰.merge()、.partial()等方法的类型推断。
  • 无效的partial调用:对已经设置了默认值的notes字段单独调用.partial()是多余的,默认值逻辑本身就支持入参缺省。

修复方案

  1. 区分输入/输出类型:入参类型使用z.input获取,parse后的返回值使用z.infer获取,不要混用。
  2. 移除.default()前冗余的.optional()调用,简化Schema定义。
  3. 删除多余的.partial()调用,默认值逻辑已经能满足入参可选的需求。

修复后的代码:

import { Timestamp } from 'firebase/firestore';
import { z } from 'zod';

export const someSchema = z.object({
  id: z.string(),
  timestamp: z.instanceof(Timestamp),
  type: z.enum(['fever', 'constipation']),
  notes: z.string().default(''),
});

export const someInput = someSchema
  .omit({ id: true })
  .merge(
    z.object({
      timestamp: z.date().default(() => new Date()),
    }),
  );

export const schemaArray = z.array(someSchema);

export type Schema = z.infer<typeof someSchema>;
// 入参类型使用z.input获取,此时timestamp、notes均为可选字段
export type SchemaInput = z.input<typeof someInput>;


function a({ type, timestamp, notes}: SchemaInput){
  // parse返回值为输出类型,timestamp、notes为必填值,直接使用即可
  const parsedData = someInput.parse({
    type, timestamp, notes
  })
}

// 调用正常,无TS报错
a({type: 'fever'})

补充说明:如果需要对入参和出参做更灵活的类型裁剪,也可以在.default()后手动调用.optional()标记输入侧可选,但优先使用官方提供的z.input/z.infer区分两类类型,能避免90%以上的默认值类型不匹配问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 09:45:20