Object型で特定のプロパティのみを動的に必須にする方法
2024-08-01

Object型で特定のプロパティのみを動的に必須にする方法

#typescript

結論

以下の型定義を利用します。

/**
 * オブジェクト型から、各プロパティを1つだけ必須とし、他のプロパティを持たないオブジェクト型に変換するマップ型を作成する。
 *
 * @template T - 変換する元のオブジェクト型。
 */
type SingleKeyObject<T> = {
  [P in keyof T]: { [O in keyof Omit<T, P>]?: never } & Required<Pick<T, P>>
}[keyof T]

使用例

type SingleKeyObject<T> = {
  [P in keyof T]: { [O in keyof Omit<T, P>]?: never } & Required<Pick<T, P>>
}[keyof T]
 
type FooBarBaz = {
  foo: string
  bar: number
  baz: boolean
}
 
// barやbazも定義するとエラーになる
const onlyFooObj: SingleKeyObject<FooBarBaz> = {
  foo: 'foo',
}
 
// fooやbazも定義するとエラーになる
const onlyBarObj: SingleKeyObject<FooBarBaz> = {
  bar: 99,
}

解説

上記のSingleKeyObject型は以下の機能を組み合わせて、実装されています。

  • Generics
  • Type Operator
    • keyof
    • typeof
  • Utility Types
    • Pick
    • Omit
    • Required
  • Indexed Access Types
  • Mapped Types
  • Conditional Types
  • in Keyword

知らない機能があったら、ChatGPTで調べましょう!

ネストが深いので、細かい部分から解説します。

スケール小

最初はこのスケールで読んでいきます。

{ [O in keyof Omit<T, P>]?: never }
T = {
  foo: string,
  bar: number,
  baz: boolean,
}
P = 'foo'

の場合

Omit<T, P> = {
  bar: number,
  baz: boolean
}
keyof Omit<T, P> = "bar" | "baz"

となり、in keywordとMapped Typesによって複数キーとして展開されます。

{
  bar?: never,
  baz?: never
}

これがスケール小の型定義の実態となります。

スケール中

さらにスケールを大きくして読んでみます。

{
  [P in keyof T]: { [O in keyof Omit<T, P>]?: never } & Required<Pick<T, P>>
}

同じように、Mapped Typesを読み解くと以下のようになります。

{
  foo: {
    foo: string,
    bar?: never,
    baz?: never
  },
  bar: {
    foo?: never,
    bar: number,
    baz?: never
  },
  baz: {
    foo?: never,
    bar?: never,
    baz: boolean
  },
}

スケール大

最後に、型定義全体を読んでみます。

{
  [P in keyof T]: { [O in keyof Omit<T, P>]?: never } & Required<Pick<T, P>>
}[keyof T]

3行目でIndexed Access Typesとkeyofを併用しています。
この部分は、Tのキーをユニオン型として展開して、それぞれのキーの値を取得するような動きをします。

  {
    foo: string,
    bar?: never,
    baz?: never
  } | {
    foo?: never,
    bar: number,
    baz?: never
  } | {
    foo?: never,
    bar?: never,
    baz: boolean
  }

これで完成です。🎉
最初の例ではこの型定義が動的に生成されていました。

まとめ

今回は「Object型で特定のプロパティのみを動的に必須にする方法」を紹介しました。
利用できるシーンでどんどん採用していきましょう!
Happy Coding!!👍

© 2024 Both Arms. All Rights Reserved.