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

TypeScript分布式条件类型未按预期工作问题求助

解决TypeScript查询类型中字符串字段value类型不匹配的问题

原类型定义

type MatchTypeDefault = 'NULL' | 'NOT_NULL';

type MatchTypeBoolean = 'IS';

type MatchTypeText =
    | 'EQUALS'
    | 'EQUALS_CASE_INSENSITIVE'
    | 'NOT_EQUALS'
    | 'NOT_EQUALS_CASE_INSENSITIVE'
    | 'BEFORE'
    | 'AFTER';

type MatchTypeNumber =
    | 'EQUALS'
    | 'NOT_EQUALS'
    | 'GREATER_THAN'
    | 'GREATER_THAN_OR_EQUAL'
    | 'LESS_THAN'
    | 'LESS_THAN_OR_EQUAL';

type MatchTypeArray = MatchTypeDefault | 'INCLUDES' | 'EXCLUDES';

type MatchTypeInArray = MatchTypeDefault | 'IN' | 'IN_CASE_INSENSITIVE';

type OrderParamsOrderBy<T extends {}> = {
    fieldName: keyof T;
    type: 'ASC' | 'DESC';
};

type QueryParamsTopLevel<TObject extends {}> = {
    conditionType: 'AND' | 'OR';
    conditions: GenericQuery<TObject>[];
    fieldName?: never;
    matchType?: never;
    value?: never;
    orderBy: OrderParamsOrderBy<TObject>[];
};

type QueryParamsCondition<
    TObject extends {},
    TKey extends keyof TObject = keyof TObject,
    TMatchType extends
        | MatchTypeText
        | MatchTypeNumber
        | MatchTypeBoolean
        | MatchTypeInArray
        | MatchTypeArray
        | MatchTypeDefault = NonNullable<TObject[TKey]> extends boolean
        ? MatchTypeBoolean
        : NonNullable<TObject[TKey]> extends string
        ? MatchTypeText | MatchTypeInArray
        : NonNullable<TObject[TKey]> extends number
        ? MatchTypeNumber
        : NonNullable<TObject[TKey]> extends Array<unknown>
        ? MatchTypeArray
        : MatchTypeDefault
> = {
    conditionType?: never;
    conditions?: never;
    fieldName: TKey;
    matchType: TMatchType;
    value: TMatchType extends MatchTypeInArray
        ? string[]
        : NonNullable<TObject[TKey]>;
    orderBy: OrderParamsOrderBy<TObject>[];
};

export type GenericQuery<TObject extends {}> = {
    [TKey in keyof TObject]:
        | QueryParamsTopLevel<TObject>
        | QueryParamsCondition<TObject, TKey>;
}[keyof TObject];

问题现象

使用测试接口TestType创建查询常量时,数字、布尔字段的matchType与value类型匹配正常,但字符串字段(如id)的value类型始终为string | string[],不符合预期:

interface TestType {
    id: string;
    price: number;
    inStock: boolean;
}

// 不符合预期:matchType为'EQUALS'时,value应该只能是string,而非string | string[]
const testVariable: GenericQuery<TestType> = {
    fieldName: 'id',
    matchType: 'EQUALS',
    value: ..., 
};

尝试的无效方案

曾尝试用非分布式条件类型修改QueryParamsCondition的value判断,但结果value始终为string,问题未解决:

type QueryParamsCondition<
    TObject extends {},
    TKey extends keyof TObject = keyof TObject,
    TMatchType extends
        | MatchTypeText
        | MatchTypeNumber
        | MatchTypeBoolean
        | MatchTypeInArray
        | MatchTypeArray
        | MatchTypeDefault = NonNullable<TObject[TKey]> extends boolean
        ? MatchTypeBoolean
        : NonNullable<TObject[TKey]> extends string
        ? MatchTypeText | MatchTypeInArray
        : NonNullable<TObject[TKey]> extends number
        ? MatchTypeNumber
        : NonNullable<TObject[TKey]> extends Array<unknown>
        ? MatchTypeArray
        : MatchTypeDefault
> = {
    conditionType?: never;
    conditions?: never;
    fieldName: TKey;
    matchType: TMatchType;
    value: [TMatchType] extends [MatchTypeInArray] // 修改此处
        ? string[]
        : NonNullable<TObject[TKey]>;
    orderBy: OrderParamsOrderBy<TObject>[];
};

正确解决方案

核心问题是原QueryParamsCondition的TMatchType为联合类型,导致条件判断生成分布式的联合类型。需针对每个matchType单独生成对应类型,再合并为联合:

步骤1:定义字段类型对应的匹配类型集合

type MatchTypeFor<T> =
  T extends boolean ? MatchTypeBoolean :
  T extends string ? MatchTypeText | MatchTypeInArray :
  T extends number ? MatchTypeNumber :
  T extends Array<unknown> ? MatchTypeArray :
  MatchTypeDefault;

步骤2:重构QueryParamsCondition

type QueryParamsCondition<TObject extends {}, TKey extends keyof TObject> =
  {
    [M in MatchTypeFor<NonNullable<TObject[TKey]>>]: {
      conditionType?: never;
      conditions?: never;
      fieldName: TKey;
      matchType: M;
      value: M extends MatchTypeInArray ? string[] : NonNullable<TObject[TKey]>;
      orderBy: OrderParamsOrderBy<TObject>[];
    }
  }[MatchTypeFor<NonNullable<TObject[TKey]>>];

步骤3:保持GenericQuery不变

export type GenericQuery<TObject extends {}> = {
  [TKey in keyof TObject]:
    | QueryParamsTopLevel<TObject>
    | QueryParamsCondition<TObject, TKey>;
}[keyof TObject];

验证效果

// 正确:matchType为'EQUALS'时,value必须是string
const test1: GenericQuery<TestType> = {
  fieldName: 'id',
  matchType: 'EQUALS',
  value: '123',
};

// 正确:matchType为'IN'时,value必须是string[]
const test2: GenericQuery<TestType> = {
  fieldName: 'id',
  matchType: 'IN',
  value: ['123', '456'],
};

// 错误:matchType为'EQUALS'时不能传入数组(TypeScript会报错)
const test3: GenericQuery<TestType> = {
  fieldName: 'id',
  matchType: 'EQUALS',
  value: ['123'],
};

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 17:35:55