blog.owlcode.net

owl
TypeScript

文字列がObject型のkeyに存在してるか確認したい

2021-11-03

やりたいことはこう

type User = { id: number, name: string }
const value = 'id'

上記のコードでvalueの値がUser型のkeyのいずれかであるかを判定したい。
つまり、上記の場合idUser型のkeyに存在しているのでtrueになるif文を書くようなイメージ。

 

結論

あまり綺麗に書くことはできない。
そもそもTypeScriptの型はコンパイル時だけの存在で実行時には消えるため、型と実行時の値を比較することはできない
そのため比較したい場合User型のkeyの情報を何らかのとして保持する必要がある。

 

パターン1

一番愚直にやるならこんなイメージ。

const userProperties = ['id', 'name']
type User = { id: string; name: string }

const value = 'name'
if (userProperties.includes(value)) {
  console.log('true')
}

わかりやすいけどUser型にidnameがあるよという情報が二重管理されていて冗長な印象を受ける。
userPropertiesUser型には繋がりがないので、今後User型にphone: numberを追加する場合、値と型のどちらかに追加を忘れてもエラーで検出できない可能性がある。

 

パターン2

こんな書き方だともう少しスマートに見える。

const keys = ['id', 'name'] as const
type ArrayElement<ArrayType extends readonly unknown[]> = ArrayType[number]
type User = {
  [k in ArrayElement<typeof keys>]: string
}

const user: User = {id: '1', name: 'hoge'}

const value = 'name'
if (keys.includes(value)) {
  console.log('true') // true
}

先ほどのkeyの二重管理は無くなったが、これではUser型のプロパティが全て同じ型になってしまうため(上記だと全てstring)、使える場所が相当限られる。

 

パターン3

keyを2回記述することは避けられないが、ここまでやると一応keyの追加漏れもある程度エラーで検知できる。

type User = { id: number; name: string }
class UserClass implements User {
  id = 0
  name = ""
}

const userProperties = Object.keys(new UserClass())
console.log(userProperties) // (2) ['id', 'name']

const value = 'name'
if (userProperties.includes(value)) {
  console.log('true') // true
}

もしphone: numberの型情報をUserのみに追加した場合、UserClassUserを満たさなくなるのでエラーとなる。
逆にUserClassだけに追加した場合、userProperties.includes('phone')とした時にエラーとなる。

ただ一見して何がやりたいのかよくわからないコードになるので、現実的ではないと思う。

 

End

そもそもこれってできるんだっけ、っていうところから色々深掘りしてしまった。