递归条件类型

时间:2018-04-26 20:46:15

标签: typescript

我想以递归方式映射对象,以便将对象中的原始值转换为其他类型。

例如,我想要一个像这样的对象:

const before = { a: { c: '' }, b: [ '', { d: '' } ] }

成为这个:

const after = { a: { c: Test }, b: [ Test, { d: Test } ] }

我还假设值不是DateSymbol或null / void。只是JSON可序列化的类型,如字符串,数字等(除了null)

这是我试过的:

type ConvertToTest<T> = {
    [P in keyof T]: T[P] extends any[]
        ? ConvertToTest<T[P]>
        : T[P] extends {}
            ? ConvertToTest<T[P]>
            : Test;
}

function convert<T>(o: T): ConvertToTest<T> {
    // ...
}

这是使用Typescript 2.8中引入的conditional types

const after = convert(before)会在after.a.c的编辑器中生成string c个类型的完成,而不是Test的完成。

如何重写type ConvertToTest<T>以说服after.a.c类型为Test的打字稿?

编辑:以下是Typescript Playground link说明以上内容。

3 个答案:

答案 0 :(得分:3)

所以你需要ConvertToTest<T>类型的两件事。一个是如果T是基本类型,那么CovertToTest<T> = Test。另一种情况是,如果T不是原始的,您希望保留相同的键但转换它们的值。

为此,我只是将第一个案例添加为条件类型的一部分,然后让另一个分支使用递归映射类型:

type Primitive = string | number | boolean | null | undefined;
type ConvertToTest<T> = T extends Primitive ? Test : {
    [K in keyof T]:
        T[K] extends (infer U)[] ? ConvertToTest<U>[] :
        ConvertToTest<T[K]>;
}

使用它,您可以像这样使用它:

// For example. Replace with whatever your actual type is.
type test = {
    foo(): string;
}

declare function convertToTest<T>(obj: T): ConvertToTest<T>;
const test = convertToTest({ a: "", b: { c: true, primArr: [1, ""], objArr: [{inner: ""}] } });

test.a.foo(); // OK
test.b.c.foo(); // OK
test.b.primArr[0].foo() // OK
test.b.objArr[0].inner.foo() // OK

这是一种很好的方法,因为它可以用于任何深度的对象,并且也可以正确处理转换数组类型的元素。

答案 1 :(得分:0)

将类型更改为

type ConvertToTest<T> = {
    [P in keyof T]: T[P] extends any[]
        ? ConvertToTest<T[P]>
        : T[P] extends string
            ? Test
            : ConvertToTest<T[P]>
}

extends string代替extends {})或

type ConvertToTest<T> = {
    [P in keyof T]: T[P] extends any[]
        ? ConvertToTest<T[P]>
        : T[P] extends object
            ? ConvertToTest<T[P]>
            : Test    
}

extends object代替extends {}

似乎可以解决问题。

答案 2 :(得分:0)

你真的很亲密。这里的问题是string可分配给{}

证明这一事实here

如果您在{}之前先检查字符串,数字等,那么您可以得到您想要的内容:

type ConvertToTest<T> = {
    [P in keyof T]: T[P] extends any[]
        ? ConvertToTest<T[P]>
        : T[P] extends string
        ? Test
        : T[P] extends number
        ? Test
        : T[P] extends boolean
        ? Test
        : ConvertToTest<T[P]>
}