-
Couldn't load subscription status.
- Fork 13.1k
Description
TypeScript Version: 2.8.0-dev.20180216
Code
// Type-level filters to extract just the required or optional properties of a type
// Defns from https://github.com/Microsoft/TypeScript/pull/21919#issuecomment-365491689
type RequiredPropNames<T> = { [P in keyof T]: undefined extends T[P] ? never : P }[keyof T];
type OptionalPropNames<T> = { [P in keyof T]: undefined extends T[P] ? P : never }[keyof T];
type RequiredProps<T> = { [P in RequiredPropNames<T>]: T[P] };
type OptionalProps<T> = { [P in OptionalPropNames<T>]: T[P] };
// Some object types with different numbers of props
type P2 = {a: string, b: number}; // Two props
type P1 = {a: string}; // One prop
type P0 = {}; // No props
// Let's extract only the required properties of P0, P1, and P2
type P2Names = RequiredPropNames<P2>; // P2Names = "a" | "b" ✓😊
type P1Names = RequiredPropNames<P1>; // P1Names = "a" ✓😊
type P0Names = RequiredPropNames<P0>; // P0Names = any ?😕
type P2Props = RequiredProps<P2>; // P2Props = { a: string; b: number; } ✓😊
type P1Props = RequiredProps<P1>; // P1Props = { a: string; } ✓😊
type P0Props = RequiredProps<P0>; // P0Props = { [x: string]: any } ?😕Expected behavior:
P0Names = never and P0Props = {}
Actual behavior:
P0Names = any and P0Props = {[x: string]: any}
Notes:
@ahejlsberg helpfully came up with the RequiredProps and OptionalProps definitions above from a question I asked in #21919. They work great except when T = {}
Not 100% sure if this is a bug or not, but it's definitely counterintuitive. RequiredProps works for any object type except the empty one, when a string indexer suddenly appears. It certainly breaks the semantic expectations of RequiredProps.
Workaround:
The following version of RequiredPropNames achieves the expected behaviour by singling out the {} case:
type RequiredPropNames<T> =
keyof T extends never
? never
: {[P in keyof T]: undefined extends T[P] ? never : P}[keyof T];