TypeScriptのmapped typesでネストされた型もすべてオプショナルにする

partial<T>

TSのmapped typesを利用して提供されている便利な型partial<T>がある

type Partial<T> = {
    [P in keyof T]?: T[P];
};

これが提供されているので

例えば以下のようなfoo型のプロパティをすべてオプショナル(?:の形式)にしたかったらこうすればよい。

interface foo {
  foo: string
  bar: string
  baz: number
}

type optionalFoo = partial<foo>

とすると

interface optionalFoo {
  foo?: string | undefined
  bar?: string | undefined
  baz?: number | undefined
}

と同じことをしていることになる。

便利。

ネストがある場合問題あり

partial<T>便利なのだけど、ネストした型がある場合、ネストされたやつまではoptionalな形式に変換してくれない。ネストされたものもすべてオプショナルに変換したいときこれでは困る。

interface foo {
  foo: string
  bar: string
  baz: {
    hoge: string
    piyo: string
    fuga: number
  }
}

こういう状態のやつ

これをpartial<T>すると

interface optionalFoo {
  foo?: string | undefined
  bar?: string | undefined
  baz?: {
    hoge: string
    piyo: string
    fuga: number
  }
}

となってしまう。

解決方法

俺はネストされた型もすべてオプショナルにしたいんだよねという場合はpartial<T>がrecursiveな挙動をするようなmapped types型を自分で書いてやればOKらしい

stackoverflow.com

type NestedPartial<T> = {
    [K in keyof T]?: T[K] extends Array<infer R> ? Array<NestedPartial<R>> : NestedPartial<T[K]>
}

TS2.8以降はこうすればArrayがあっても大丈夫とのことらしい。

というわけで

type NestedPartial<T> = {
    [K in keyof T]?: T[K] extends Array<infer R> ? Array<NestedPartial<R>> : NestedPartial<T[K]>
};

interface foo {
  foo: string
  bar: string
  baz: {
    hoge: string
    piyo: string
    fuga: number
  }
}

type optionalFoo = NestedPartial<foo>

こうすればネストされた型も全部オプショナルに変換された。

ありがとうstackoverflow 🙏🙏🙏