Nicholas Tower
Nicholas Tower

Reputation: 84912

Can mapped types make properties optional, but only if a condition is met?

I'm attempting to make a mapped type which takes all properties with a certain type (in my case, arrays) and makes them optional. But any other properties (non-arrays) are left untouched. I know the optional modifier can be used when defining a mapped type, as in:

type MyPartial<T> = {
  [key in keyof T]?: T[key]
}

But i'm not sure if the syntax allows this to be combined with conditions. I can get pretty close with the following:

type OptionalArrays<T> = {
  [key in keyof T]: T[key] extends Array<any> ? T[key] | undefined : T[key]
}

interface Example {
  foo: string[];
  bar: number;
};

type Example2 = OptionalArrays<Example>;

Problem is that this results in explicit undefineds, not implicit ones, so while it will behave as i want for these two cases:

const value1: Example2 = {
  foo: [],
  bar: 3,
}

const value2: Example2 = {
  foo: undefined,
  bar: 3,
}

It will give an unwanted error that foo is missing for this one:

const value3: Example2 = {
  bar: 3,
}

Is it possible to add the optional modifier (?) in a mapped type, but only when a certain condition is met?

Playground link

Upvotes: 9

Views: 3452

Answers (1)

Alex Wayne
Alex Wayne

Reputation: 187014

It's possible there's a more elegant way, but it's not too awkward to intersect two mapped types instead. One for the optional properties, and one for the required ones.

For example:

type OptionalArrays<T> = {
  [key in keyof T as T[key] extends Array<any> ? key : never]?: T[key]
} & {
  [key in keyof T as T[key] extends Array<any> ? never : key]: T[key]
}

Note the as in the key portion of the mapped type. This let's you transform the key type. In this case we either use the actual key if we want to keep it, or transform it to never if we dont.

Playground


Or perhaps this version which is a similar, but arguably slightly less cryptic.

type ArrayKeys<T> = {
    [key in keyof T]: T[key] extends Array<any> ? key : never
}[keyof T]

type OptionalArrays<T> =
    Omit<T, ArrayKeys<T>> & // get one type without array keys
    Partial<Pick<T, ArrayKeys<T>>> // get one type with array keys as optional

Playground

Upvotes: 15

Related Questions