typescript - Omitiendo todas las propiedades en un objeto que son `undefined`
Tengo un tipo así:
{
a: number,
b: number,
c: undefined
}
Y quiero asignarlo a un tipo como este:
{
a: number,
b: number
}
Es decir, quiero omitir todas las claves donde el tipo del valor sea undefined por definición. El tipo proviene de un genérico, así que obviamente no puedo hacerlo manualmente.
Sé que puedo asignar un tipo a otro tipo con un mapeador, así que intenté asignar undefined a never:
type TryOmitUndefined<K> = {
[k in keyof K]: K[k] extends undefined ? never : K[k]
}
Pero esto no funciona en absoluto.
¿Es posible en TypeScript actual omitir claves de un tipo basándose en su valor?
Pregunta adicional
¿Puedo mapear esto?
{
a: number,
b: number | undefined,
c: undefined
}
¿a esto?
{
a: number,
b?: number
}
Respuesta #1
Probablemente sea más fácil hacer esto en dos pasos. Primero, obtén las claves indefinidas:
type UndefinedKeys<T> = {
[K in keyof T]: T[K] extends undefined ? K : never
}[keyof T]
Y ahora Omit esas claves:
type WithoutUndefined<T> = Omit<T, UndefinedKeys<T>>
Entonces debería funcionar como se espera:
type Bar = WithoutUndefined<Foo>
const bar: Bar = { a: 1, b: 2 }
Parque infantil
El tipo UndefinedKeys<T> funciona así:
El [] después de un tipo indica que se deben dar todos los posibles tipos de valores como una unión que coincida con esos tipos de clave.
type StringArray = string[]
type A = StringArray[number] // string
interface Abc { a: string, b: number, c: boolean }
type B = Abc['a' | 'b' | 'c'] // string | number | 'boolean'
type AbcKeys = keyof Abc // 'a' | 'b' | 'c'
type C = Abc[keyof Abc] // string | number | 'boolean'
Así que dado eso, mapeamos sobre un tipo para crear un nuevo tipo de objeto donde: (seudocódigo)
type KeyOrNever = { [originalKey]: originalKey or never }
O en el caso de tu ejemplo:
type KeyOrNever = { a: "a", b: "b", c: never }
Entonces, cada clave tiene un valor que es su propio nombre, o un valor de never. Ahora podemos obtener los valores de todas las propiedades como una unión indexando el tipo por sus propias claves. Esto daría como resultado:
type KeysAsUnion = "a" | "b" | never
never se descarta de las uniones porque no cumple ninguna función. No puedes ser posiblemente una cosa, o posiblemente otra cosa que nunca puede existir, por lo que simplemente se ignora. Y así la unión se simplifica a:
type KeysAsUnion = "a" | "b"
Respuesta #2
Encontré una gran solución de @ahejlsberg que me llevó la mayor parte del camino:
// https://github.com/microsoft/TypeScript/pull/21919#issuecomment-365491689
type OptionalPropNames<T> = { [P in keyof T]: undefined extends T[P] ? P : never }[keyof T];
type RequiredPropNames<T> = { [P in keyof T]: undefined extends T[P] ? never : P }[keyof T];
type OptionalProps<T> = { [P in OptionalPropNames<T>]: T[P] };
type RequiredProps<T> = { [P in RequiredPropNames<T>]: T[P] };
type Foo = {
a: string;
b: number;
c?: string;
d?: number;
}
type T1 = RequiredProps<Foo>; // { a: string, b: number }
type T2 = OptionalProps<Foo>; // { c?: string | undefined, d?: number | undefined }
Esto muestra cómo omitir claves según su valor (no estoy seguro de por qué mi solución de ejemplo no funciona; agradecería cualquier opinión), pero no maneja el caso específico en el que un tipo se establece explícitamente en undefined.
Lo amplié con esto:
type TrueUndefinedPropNames<T> = { [P in keyof T]: T[P] extends undefined ? P : never }[keyof T];
type TrueUndefinedProps<T> = { [P in TrueUndefinedPropNames<T>]: T[P] };
Entonces la solución se convierte en:
type WhatIWant<T> = Omit<
T,
TrueUndefinedPropNames<T>
>
O, si también quiero convertir las propiedades requeridas del tipo K | undefined en propiedades opcionales:
type WhatIWant2<T> = RequiredProps<T> & Omit<
Partial<OptionalProps<T>>,
TrueUndefinedPropNames<T>
>
Demostración :
type TestType = {
a: undefined;
b: number;
c: number | undefined;
}
const x: TestType = { b: 3 } // Fails
const y: WhatIWant<TestType> = { b: 3, c: undefined } // Succeeds
const z: WhatIWant2<TestType> = { b: 3 } // Succeeds!
