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

TypeScript条件类型中推断类型为何回退到默认分支?

解决express-validator Schema类型推断失败的问题

问题根源

你的类型推断逻辑在处理多类型字段时失效,核心原因是:当InferSchema中的TCurrentKey为keyof TSchema的联合类型时,TSchema[TKey]会变成所有字段ParamSchema的联合类型。此时InferPrimitive中的条件判断(比如'isString' extends keyof T)只有当联合类型里的所有ParamSchema都包含对应键时才会成立,而你的场景中不同字段有不同校验键(如isString/isInt/isObject),导致所有条件分支都不满足,最终落入默认的'ITS FAILING HERE'分支。

修复方案

修改类型推断逻辑,确保每个字段的ParamSchema被单独处理,而非作为联合类型判断。以下是调整后的完整代码:

import { Schema, ParamSchema } from 'express-validator';

// 推断单个ParamSchema的基础类型
type InferPrimitive<T extends ParamSchema> = 
  T extends { isString: any } ? string :
  T extends { isEmail: any } ? string :
  T extends { isAlpha: any } ? string :
  T extends { isAlphanumeric: any } ? string :
  T extends { isBoolean: any } ? boolean :
  T extends { isInt: any } ? number :
  T extends { isObject: any } ? {} :
  never;

// 处理可选/可空逻辑
type InferOptional<T extends ParamSchema> = 
  'optional' extends keyof T 
    ? T['optional'] extends { options: { nullable: true } }
      ? InferPrimitive<T> | null | undefined
      : InferPrimitive<T> | undefined
    : InferPrimitive<T>;

// 处理isIn枚举类型
type InferIsIn<T extends ParamSchema> = 
  T extends { isIn: { options: [infer U extends readonly any[]] } }
    ? U[number]
    : never;

// 推断单个ParamSchema的最终类型
type InferParam<T extends ParamSchema> = 
  T extends { isIn: any } ? InferIsIn<T> : InferOptional<T>;

// 将路径字符串转换为嵌套对象类型
type PathToType<Path extends string, T> = 
  Path extends `${infer K}.${infer Rest}`
    ? { [key in K]: PathToType<Rest, T> }
    : { [key in Path]: T };

// 合并多个嵌套对象类型为交叉类型
type MergePaths<T extends Schema> = 
  { [K in keyof T]: PathToType<K & string, InferParam<T[K]>> }[keyof T];

// 最终推断类型:将交叉类型合并为完整对象
type Infer<T extends Schema> = MergePaths<T> extends infer O 
  ? { [K in keyof O]: O[K] } 
  : never;

// 测试用Schema
export const schema = {
  name: {
    isString: true,
    optional: { options: { nullable: true } },
  },
  age: {
    isInt: true,
  },
  address: {
    isObject: true,
  },
  'address.address1': {
    isString: true,
  },
  'address.address2': {
    isString: true,
  },
  'address.number': {
    isInt: true,
  },
  'address.city.name': {
    isIn: { options: [['New York', 'Los Angeles'] as const] },
  },
} satisfies Schema;

// 验证推断结果
const inferred = {} as Infer<typeof schema>;
/*
推断出的正确类型:
{
  name: string | null | undefined;
  age: number;
  address: {
    address1: string;
    address2: string;
    number: number;
    city: {
      name: "New York" | "Los Angeles";
    };
  };
}
*/

关键调整点

  1. 修改InferPrimitive的判断逻辑:从基于keyof的条件判断改为直接用T extends { [key]: any }的形式,这样即使T是联合类型的一部分,单个成员也能正确匹配对应的类型分支。
  2. 重构InferSchema为MergePaths:通过映射类型遍历每个单独的键,将每个路径转换为对应的嵌套对象类型,再通过交叉类型合并所有嵌套对象,最后转换为完整的扁平嵌套对象。
  3. 确保单个字段的独立处理:每个字段的ParamSchema被单独传入InferParam进行推断,避免了联合类型导致的条件判断失效问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 02:49:50