Davide Valdo
Davide Valdo

Reputation: 779

Conditionally apply ? modifier in mapped type per-property

From the TypeScript docs:

// Removes 'optional' attributes from a type's properties
type Concrete<Type> = {
  [Property in keyof Type]-?: Type[Property];
};

type MaybeUser = {
  id: string;
  name?: string;
  age?: number;
};

I know I can add the ? modifier to all properties, what if I want to add it property by property based on a extends expression?

Something that would behave like this:

// Not valid TypeScript
type Optionalize<T> = {
   [P in keyof T](?: T[P] extends SomeInterface): T[P];
}   

Upvotes: 2

Views: 1089

Answers (1)

Davide Valdo
Davide Valdo

Reputation: 779

Relevant GitHub issue: https://github.com/microsoft/TypeScript/issues/32562

  1. Extract the non matching props in a type, let's call it NonMatching
  2. Extract the matching prop in a second type, say Matching
  3. Intersect the two types using the extends infer technique
type TestType = {
  a: SomeInterface;
  b: string;
};

type Intersection<A, B> = A & B extends infer U
  ? { [P in keyof U]: U[P] }
  : never;

type Matching<T, SomeInterface> = {
  [K in keyof T]: T[K] extends SomeInterface ? K : never;
}[keyof T];

type NonMatching<T, SomeInterface> = {
  [K in keyof T]: T[K] extends SomeInterface ? never : K;
}[keyof T];

type DesiredOutcome = Intersection<
  Partial<Pick<TestType, Matching<TestType, SomeInterface>>>,
  Required<Pick<TestType, NonMatching<TestType, SomeInterface>>
>

{[K in keyof T]: T[K] extends SomeInterface ? K : never } it's kind of a workaround to map every matching key to it's own string literal type representation, i.e. given { a: never, b: SomeInterface } you get { b: 'b' }, then using indexed access types you get the matching properties in the form of union of string literal types

Upvotes: 2

Related Questions