※201911/06追記
npmに今回の内容をパッケージ化したhasproperty-tsを登録しました
[TypeScript]Objectのプロパティチェックと自動型生成
※同じ記事をこちらにも書いています
TypeScriptでオブジェクトにプロパティが存在するかどうかを確認する場合には、inキーワードが使用できます。しかしinでメンバを判別しても前提の型情報が存在しないと、判別したプロパティを使用することが出来ません。これを解決する方法を紹介します。
1.エラーが出る例
function Test01(x:unknown){
if(x instanceof Object && "foo" in x){
console.log(x.foo); //Error
console.log((<{foo:unknown}>x).foo); //OKだけど不便
}
}
xの型情報をunknownにした状態でfooがプロパティとして判定するためには、まずxのインスタンスがObjectかどうか判別する必要があります。Objectならばinキーワードを利用して、fooプロパティがあるかどうかを確認していきます。
しかしメンバにxがあることが分かっても、ifブロックの中でのxの型はneverとされてしまい、fooを使用するとエラーとなってしまいます。fooがあると分かっていても、いったんオブジェクト側をキャストしないとメンバを使用することは出来ません。これはひじょうに不便な状態です。
2.そもそものinの用途
そもそもTypeScriptでinを有効に使うためには、事前に前提となる型情報を与えておく必要があります。unionのように複数の候補の中から目的の型を選べるようにしておかなければなりません。
function Test02(x: { foo1: unknown,bar1:unknown } | { foo2: unknown,bar2:unknown }) {
if ("foo1" in x) {
console.log(x.foo1); //OK
console.log(x.bar1); //OK
console.log(x.foo2); //Error
console.log(x.bar2); //Error
}
}
3.プロパティチェックを使いやすくする
このままでは面倒なので、プロパティのチェックと型の生成を同時に行うfunctionを作ってみます。
function hasProperty<K extends string>(
x: unknown,
name: K
): x is { [M in K]: unknown } {
return x instanceof Object && name in x;
}
function Test03(x: unknown) {
if (hasProperty(x, "foo")) {
console.log(x.foo); //キャスト無しでFooが使える
}
}
hasPropertyが真を返すと、判定したプロパティを持つオブジェクトとして型が決定します。これで毎回手動でオブジェクト側の型をキャストする必要がなくなります。
4.複数同時にチェックできるようにする
プロパティをまとめてチェック出来るように、nameを可変引数にします
function hasProperty2<K extends string>(
x: unknown,
...name: K[]
): x is { [M in K]: unknown } {
return (
x instanceof Object && name.every(prop => prop in x)
);
}
function Test04(x: unknown) {
if (hasProperty2(x, "foo", "bar")) {
console.log(x.foo); //Fooが使える
console.log(x.bar); //Barが使える
}
}
これでオブジェクト側に余計なキャストをイチイチ書かなくとも、直接プロパティを扱えるようになりました。
5.まとめ
プロパティの存在チェックと型の生成は標準でサポートされていても良さそうな気がしますが、無いなら自分で作れるのがTypeScriptの良いところです。