Рекурсивные условные типы - PullRequest
0 голосов
/ 26 апреля 2018

Я хотел бы отобразить объект рекурсивно так, чтобы примитивные значения в объекте были преобразованы в некоторый другой тип.

Например, я бы хотел такой объект:

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

чтобы стать таким:

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

Я также предполагаю, что значения не будут Date, Symbol или null / void. Просто сериализуемые типы JSON, такие как строка, числа и т. Д. (Кроме нуля)

Вот что я попробовал:

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.

const after = convert(before) приводит к after.a.c с дополнениями типа string в редакторе для c вместо дополнений для Test.

Как мне переписать type ConvertToTest<T>, чтобы убедить Typescript в том, что after.a.c имеет тип Test?

РЕДАКТИРОВАТЬ: Вот ссылка Typescript Playground , иллюстрирующая выше.

Ответы [ 3 ]

0 голосов
/ 27 апреля 2018

Вы были действительно близко. Проблема в том, что string назначается на {}.

Демонстрация этого факта здесь .

Если сначала проверить строку, число и т. Д. До {}, то вы можете получить то, что хотите:

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]>
}
0 голосов
/ 27 апреля 2018

Итак, вам нужны две вещи типа 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

Это хороший способ сделать это, так как он будет работать для объектов любой глубины и будет правильно обрабатывать преобразование элементов типа массива.

0 голосов
/ 27 апреля 2018

изменение типа на

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 {})

кажется, добивается цели.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...