我有数据类型的下一个数据
type Data = {
Id: string,
LogicalName: string,
VATRegistered: {
Label: string | null,
Value: number | null,
}
}
const data: Data = {
Id: 'qK1jd828Qkdlqlsz8123assaa',
LogicalName: 'locale',
VATRegistered: {
Label: 'AT401',
Value: 1000001
}
}
Run Code Online (Sandbox Code Playgroud)
我必须将其转换为下一个:
const transformedData = {
Id: 'qK1jd828Qkdlqlsz8123assaa',
LogicalName: 'locale',
VATRegisteredLabel: 'AT401',
VATRegisteredValue: 1000001
}
Run Code Online (Sandbox Code Playgroud)
我编写了一个函数,它必须转换我的对象并以下一个类型返回它
type TransformedData {
Id: string,
LogicalName: string,
VATRegisteredLabel: string | null,
VATRegisteredValue: number | null
}
Run Code Online (Sandbox Code Playgroud)
我的功能:
const _isNull = (value: any) => {
let res = value === null || undefined ? null : value;
return res
};
function transformData<T extends {}, U extends {}>(obj: T, fatherName: keyof U | undefined) {
let newObj;
for (let key in obj) {
let k = obj[key];
if (typeof k === 'object' && !Array.isArray(k) && k !== null) {
Object.assign(newObj, transformData<typeof k, T>(k, key))
} else {
Object.assign(newObj, { [fatherName ? fatherName + key : key]: _isNull(k) })
}
}
return newObj;
}
Run Code Online (Sandbox Code Playgroud)
但我获得了一个具有空对象类型的新对象。有没有办法重写函数,使其返回 TransformedData 类型的新对象?
coy*_*508 17
这是一个更详细但更易读的实现:
// Returns R if T is a function, otherwise returns Fallback
type IsFunction<T, R, Fallback = T> = T extends (...args: any[]) => any ? R : Fallback
// Returns R if T is an object, otherwise returns Fallback
type IsObject<T, R, Fallback = T> = IsFunction<T, Fallback, (T extends object ? R : Fallback)>
// "a.b.c" => "b.c"
type Tail<S> = S extends `${string}.${infer T}`? Tail<T> : S;
// typeof Object.values(T)
type Value<T> = T[keyof T]
// {a: {b: 1, c: 2}} => {"a.b": {b: 1, c: 2}, "a.c": {b: 1, c: 2}}
type FlattenStepOne<T> = {
[K in keyof T as K extends string ? (IsObject<T[K], `${K}.${keyof T[K] & string}`, K>) : K]:
IsObject<T[K], {[key in keyof T[K]]: T[K][key]}>
};
// {"a.b": {b: 1, c: 2}, "a.c": {b: 1, c: 2}} => {"a.b": {b: 1}, "a.c": {c: 2}}
type FlattenStepTwo<T> = {[a in keyof T]: IsObject<T[a], Value<{[M in keyof T[a] as M extends Tail<a> ? M : never]: T[a][M] }>>}
// {a: {b: 1, c: {d: 1}}} => {"a.b": 1, "a.c": {d: 1}}
type FlattenOneLevel<T> = FlattenStepTwo<FlattenStepOne<T>>
// {a: {b: 1, c: {d: 1}}} => {"a.b": 1, "a.b.c.d": 1}
type Flatten<T> = T extends FlattenOneLevel<T> ? T: Flatten<FlattenOneLevel<T>>
Run Code Online (Sandbox Code Playgroud)
它不支持所有边缘情况,例如可选属性,但使其适应您遇到的特定边缘情况应该不会太难。
例如,删除键名称中的点,如代码中所示:
// "a.b.c" => "abc"
type RemoveDots<S> = S extends `${infer H}.${infer T}`? RemoveDots<`${H}${T}`> : S;
type FlattenWithoutDots<T> = {[K in Flatten<T> as RemoveDots<K>]: Flatten<T>[K]};
Run Code Online (Sandbox Code Playgroud)
jca*_*alz 11
我将这个问题解释为:“如何在 TypeScript 中采用对象类型,例如
type Data = {
Id: string,
LogicalName: string,
VATRegistered: {
Label: string | null,
Value: number | null,
SomethingElse: {
Hello: number
}
}
}
Run Code Online (Sandbox Code Playgroud)
并将其递归地展平为对象类型,例如:
type TransformedData = {
Id: string,
LogicalName: string,
VATRegisteredLabel: string | null,
VATRegisteredValue: number | null,
VATRegisteredSomethingElseHello: number
}
Run Code Online (Sandbox Code Playgroud)
这样所有属性都是非对象类型,并且新类型中的每个键都是结果属性的串联键路径?”
我只想说这是可能的,但很脆弱而且丑陋得可怕。TypeScript 4.1 为您提供了递归条件类型、模板文字类型以及映射类型中的键重新映射,所有这些都是您所需要的。从概念上讲,对于Flatten一个对象,您希望获取该对象的每个属性并按原样输出它们(如果它们是基元或数组),否则输出Flatten。属性Flatten就是将属性键添加到扁平化属性的键之前。
这或多或少是我采用的方法,但是您必须跳过很多环节(例如,避免递归限制、并集到交集、交集到单个对象、避免symbol键连接中的键等)甚至很难开始更详细地解释它,而且有很多边缘情况和警告(例如,我预计可选属性、索引签名或与至少一个联合的属性类型会发生不好的事情)对象类型成员),我不愿意在生产环境中使用这样的东西。无论如何,这就是它的全部荣耀:
type Flatten<T extends object> = object extends T ? object : {
[K in keyof T]-?: (x: NonNullable<T[K]> extends infer V ? V extends object ?
V extends readonly any[] ? Pick<T, K> : Flatten<V> extends infer FV ? ({
[P in keyof FV as `${Extract<K, string | number>}${Extract<P, string | number>}`]:
FV[P] }) : never : Pick<T, K> : never
) => void } extends Record<keyof T, (y: infer O) => void> ?
O extends infer U ? { [K in keyof O]: O[K] } : never : never
Run Code Online (Sandbox Code Playgroud)
然后你的transformData()函数可以得到以下调用签名(我正在使用重载,并且只关心当你不带fatherName参数调用它时的行为。其余的我将给出any:
function transformData<T extends object>(obj: T): Flatten<T>;
function transformData(obj: any, fatherName: string | number): any
function transformData(obj: any, fatherName?: string | number): any {
let newObj = {};
for (let key in obj) {
let k = obj[key];
if (typeof k === 'object' && !Array.isArray(k) && k !== null) {
Object.assign(newObj, transformData(k, key))
} else {
Object.assign(newObj, { [fatherName ? fatherName + key : key]: _isNull(k) })
}
}
return newObj;
}
Run Code Online (Sandbox Code Playgroud)
让我们看看它是如何工作的data:
const data: Data = {
Id: 'qK1jd828Qkdlqlsz8123assaa',
LogicalName: 'locale',
VATRegistered: {
Label: 'AT401',
Value: 1000001,
SomethingElse: {
Hello: 123
}
}
}
const transformed = transformData(data);
/* const transformed: {
Id: string;
LogicalName: string;
VATRegisteredLabel: string | null;
VATRegisteredValue: number | null;
VATRegisteredSomethingElseHello: number;
} */
console.log(transformed);
/* {
"Id": "qK1jd828Qkdlqlsz8123assaa",
"LogicalName": "locale",
"VATRegisteredLabel": "AT401",
"VATRegisteredValue": 1000001,
"SomethingElseHello": 123
} */
Run Code Online (Sandbox Code Playgroud)
万岁,编译器认为它transformed是相同的类型,TransformedData即使我没有这样注释它。键在类型和对象中串联起来。
那么,就这样吧。再说一次,我真的只建议将其用于娱乐目的,以此来了解我们可以将类型系统推向多远。对于任何生产用途,我可能只是硬编码该类型的调用签名(obj: Data) => TransformedData(如果您正在使用它),或者甚至可能坚持下去any,只是告诉人们在调用它时需要编写自己的类型。
| 归档时间: |
|
| 查看次数: |
2085 次 |
| 最近记录: |