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

如何动态定义类型实现查询过滤器归一化工具函数?

归一化查询过滤器泛型函数优化需求

核心需求

需要创建一个泛型函数,实现查询过滤器的归一化处理:

  • 输入:一个包含可选属性的对象,属性值可为单个字符串或字符串数组,示例类型如下:
interface QueryFilter {
  id?: string | string[];
  sku?: string | string[];
  name?: string | string[];
}
  • 输出:一个包含hasX和getX方法的工具对象,示例类型如下:
interface QueryFilterNormalized {
  hasId: () => boolean;
  getId: () => string[];
  hasSku: () => boolean;
  getSku: () => string[];
  hasName: () => boolean;
  getName: () => string[];
}
  • 归一化规则:输入对象中不存在的属性返回空数组,单个字符串属性转为单元素数组。

已尝试的两种实现方案

方案一:基于输入对象遍历生成工具对象

直接遍历输入对象生成工具对象,但存在缺陷:仅对输入对象中实际存在的属性生效,调用t.hasName()这类未在输入中出现的属性方法会报错;虽类型定义正确,但实现不够简洁。代码如下:

type QueryObject = {
  [key: string]: string | string[] | undefined;
};

interface QueryFilter extends QueryObject {
  id?: string | string[];
  sku?: string | string[];
  name?: string | string[];
}

type QueryObjectNorm<T extends QueryObject> = {
  [Property in keyof T as `has${Capitalize<
    string & Property
  >}`]-?: () => boolean;
} & {
  [Property in keyof T as `get${Capitalize<
    string & Property
  >}`]-?: () => string[];
};

function norm<T extends QueryObject>(object: T): QueryObjectNorm<T> {
  const obj: { [key: string]: any } = {};
  for (const i in object) {
    const key = i.charAt(0).toUpperCase() + i.slice(1);
    let querystr: string[] = [];
    const value = object[i];
    if (value) {
      querystr = querystr.concat(value);
    }
    obj['has' + key] = () => querystr.length > 0;
    obj['get' + key] = () => querystr;
  }
  return obj as QueryObjectNorm<T>;
}

const queryObject = {
  id: 'A',
  sku: ['B', 'C']
}

const t = norm<QueryFilter>(queryObject);
console.log(t.getId())
console.log(t.getSku())
console.log(t.hasName()) // 此处会报错

方案二:预定义属性数组生成类型与工具对象

通过预定义属性数组来生成类型和工具对象,能得到正确输出与类型支持,但类型定义和类型转换部分不够简洁。由于不同查询需要不同过滤器,需要通用泛型函数,现寻求优化。代码如下:

type QueryObject = {
  [key: string]: string | string[] | undefined;
};

type ObjectArr<T extends Readonly<Array<string>>> = {
  [key in T[number]]: string | string[];
};

type QueryObjectNorm<T> = {
  [Property in keyof T as `has${Capitalize<
    string & Property
  >}`]-?: () => boolean;
} & {
  [Property in keyof T as `get${Capitalize<
    string & Property
  >}`]-?: () => string[];
};

function norm<T extends QueryObject>(
  keys: readonly (keyof T)[],
  object: Partial<T>,
): QueryObjectNorm<T> {
  const obj: { [key: string]: any } = {};
  for (const i of keys) {
    let key = i as string;
    key = key.charAt(0).toUpperCase() + key.slice(1);
    let querystr: string[] = [];
    const value = object[i];
    if (value) {
      querystr = querystr.concat(value);
    }
    obj['has' + key] = () => querystr.length > 0;
    obj['get' + key] = () => querystr;
  }
  return obj as QueryObjectNorm<T>;
}

const queryObject = {
  id: 'A',
  sku: ['B', 'C']
}

const queryFilter = ['id', 'sku', 'name'] as const;

const t = norm<ObjectArr<typeof queryFilter>>(queryFilter, queryObject);
console.log(t.getId())
console.log(t.getSku())
console.log(t.hasName())

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 06:27:47