Хорошо, это длинный и очень специфический. Буду очень признателен за любой вклад.
Я написал рекурсивный тип Omit
, который принимает тип T
и кортеж строк («путь») и индексирует в T
, удаляя последний элемент на пути и возвращая его тип.
Я уже посмотрел на Deep Omit With Typescript - но это касается рекурсивного пропуска одного и того же ключа, а не обхода по пути.
Я также рассмотрел Тип безопасной функции пропуска , но он касается времени выполнения и почти пропущен на один уровень.
Моя реализация:
// for going along the tuple that is the path
type Tail<T extends any[]> = ((...args: T) => void) extends ((head: unknown, ...rest: infer U) => void) ? U : never;
type DeepOmit<T, Path extends string[]> = T extends object ? {
0: Omit<T, Path[0]>;
1: { [K in keyof T]: K extends Path[0] ? DeepOmit<T[K], Tail<Path>> : T[K] }
}[Path['length'] extends 1 ? 0 : 1] : T;
Я хочу сделать так, чтобы Path
ограничивался допустимым обходом T
, что-то вроде:
Path = [K1, K2, K3, ..., K_n] such that:
K1 extends keyof T, K2 extends keyof T[K1], ... Kn extends keyof[T][K1]...[K_(n-1)]
Первая попытка
Я написал тип, который принимает тип T
и путь P
и возвращает P
, если он действителен, или never
в противном случае:
type ExistingPathInObject<T, P extends any[]> = P extends [] ? P : {
0: ExistingPathInObject<T[P[0]], Tail<P>> extends never ? never : P;
1: never;
}[P[0] extends keyof T ? 0 : 1]
Однако в подписи DeepOmit
я не могу применить Path extends ExistingPathInObject<T,Path>
, поскольку это циклическая зависимость. Я упоминаю об этой попытке, потому что мог бы быть способ обойти цикличность и использовать этот тип, чтобы проверить Path
как действительный обход T
.
Вторая попытка
Поскольку я не могу использовать Path
для ограничения себя, я вместо этого попытался создать объединение всех существующих путей в типе, а затем потребовал Path
для его расширения. Лучшее, что я мог придумать, это:
type Paths<T> = {
0: { [K in keyof T]: T[K] extends object ? [K, Paths<T[K]>[keyof Paths<T[K]>]] : [K] }
1: []
}[T extends object ? 0 : 1];
// an example type to test on
type HasNested = { a: string; b: { c: number; d: string } };
type pathTest = Paths<HasNested>;
//{
// a: ["a"];
// b: ["b", ["d"] | ["c"]];
//}
type pathTestUnion = Paths<HasNested>[keyof Paths<HasNested>]
// ["a"] | ["b", ["d"] | ["c"]]
Это позволяет мне сопоставить путь, записанный в виде дерева: ['a'] extends pathTestUnion
и ['b', ['d']] extends pathTestUnion
оба имеют значение true. Мне нужно добавить [keyof T]
, чтобы получить объединение, и я не могу поместить его в тип Paths
, потому что он не распознается как действительный.
После всего этого у меня возникли трудности с переписыванием DeepOmit
, чтобы использовать это ограничение. Вот что я попробовал:
type Types<T> = T[keyof T];
type TypeSafeDeepOmit<T, Path extends Types<Paths<T>>, K extends keyof T> =
Path extends any[] ?
T extends object ?
{ // T is object and Path is any[]
0: Omit<T, K>;
1: { [P in keyof T]: P extends Path[0] ? TypeSafeDeepOmit<T[P], Path[1], Path[1][0]> : T[P] }
}[Path['length'] extends 1 ? 0 : 1] :
T : // if T is not object
never; // if Path is not any[]
type TSDO_Helper<T, P extends Types<Paths<T>>> = P extends any[] ? P[0] extends keyof T ? TypeSafeDeepOmit<T, P, P[0]> : never : never;
Это уродливо и использует вспомогательный тип для фактической работы. Я также должен сообщить компилятору, что P extends any[]
и P[0] extends keyof T
, хотя именно это и должно гарантировать Paths
. Я также получаю сообщение об ошибке при рекурсивном вызове TypeSafeDeepOmit
с использованием Path[1]
-
Type 'any[] & Path' is not assignable to type '[]'.
Types of property 'length' are incompatible.
Type 'number' is not assignable to type '0'
Я исправил это, установив Path extends Types<Paths<T>> | []
, но я не уверен, что это правильный путь.
В итоге
Итак, есть ли более приятный способ применения правильного пути? Можно ли также поддерживать объединение путей, чтобы пропустить их все? Прямо сейчас результат, который я получаю, представляет собой объединение результатов различных пропусков.