如何动态定义类型实现查询过滤器归一化工具函数?
归一化查询过滤器泛型函数优化需求
核心需求
需要创建一个泛型函数,实现查询过滤器的归一化处理:
- 输入:一个包含可选属性的对象,属性值可为单个字符串或字符串数组,示例类型如下:
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
相关产品推荐
相关产品推荐

