🕶️
Typescript で 型(構造体) の一致を判定する (Class, Interface, Type)
やりたいこと
とあるオブジェクトが Class
、 Interface
または Type
の型を満たすのか判定したい。
コードで
こんな感じのイメージ
class Target {
id: number = 0;
name: string = "taro";
}
const obj = {
id: 3,
name: "jiro",
}
type isTarget = (arg: unknown) => boolean;
console.log(isTarget(obj))
// => true
ポイント
1. typeof
typeof で判定できるのは object | boolean | number | string | undefined | function
等で、構造の判定には使えない
今回は、Class, Interface, Type の判定ということで、これだけで直接判定することはできない。
2. instanceof
instanceof は、クラスインスタンス(例: new Target()
)の判定かつ、判定先がクラス(例: class Target {}
)の時に使用できる。
instanceof 演算子は、オブジェクトが自身のプロトタイプにコンストラクタの prototype プロパティを持っているかを確認します。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/instanceof
ここからわかるように、オブジェクトの構造自体を直接検証しているわけではないことに注意する。
3. ユーザー定義型ガード
ユーザー定義型ガード の仕組みを使うべし
1, 2 が使えない構造の一致判定はJavaScriptには非常に豊富な実行時の解析サポートが組み込まれていません。単純なJavaScriptオブジェクトだけを使用している場合(構造型を使用する場合)、 instanceofまたはtypeofにアクセスすることさえできません。
https://typescript-jp.gitbook.io/deep-dive/type-system/typeguard#yznotype-guard
オブジェクトの内部構造を検証して型の一致を確認したい場合、この方法を使うことになる。
判定してみる
テストツールには Jest を使用する。
Class の判定
instanceof を使う。
describe("型検証", () => {
// 検証先はクラス
class Target {
id: number = 0;
name: string = "taro";
}
it("ポイント2の方法が使える", () => {
const target = new Target();
expect(target instanceof Target).toBeTruthy();
});
}
Interface, Type, Class の判定
ユーザー定義型ガード と typeof を組み合わせて検証する。
型の定義
まずは、検証を行いたい型を定義する。
// Class の他に、Interface, Type で書ける
class Target {
id: number =0;
name: string = "taro";
}
// interface Target {
// id: number;
// name: string;
// }
// type Target = {
// id: number;
// name: string;
// }
ユーザー定義型ガード の作成
構造体の型を検証するための ユーザー定義型ガード を実装する。
const isTarget = (arg: unknown): arg is Target =>
typeof arg === "object" &&
arg !== null &&
// as Target で型の予測を効かせて typo を防ぐ
typeof (arg as Target).id === "number" &&
typeof (arg as Target).name === "string";
検証してみる
Valid
// オブジェクト作成
const validObj = {
id: 1,
name: "saburo",
};
// 検証
it("valid", () => {
expected(isTarget(validObj)).toBeTruthy();
// ここから validObj を Target 型のオブジェクトとして型推論が使える
expect(validObj.id).toBe(1);
expect(validObj.name).toBe("saburo");
})
InValid
// オブジェクト作成
const invalidObj = {
a: "invalid",
b: "jiro",
};
// 検証
it("invalid", () => {
expect(isTarget(validObj)).toBeFalsy();
})
さいごに
これで、Typescript の型とまた一歩仲良くなれた。
使用したテストの全体
テストコード
describe("型検証", () => {
// Class の他に、Interface, Type で書ける
class Target {
id: number = 0;
name: string = "taro";
}
// interface Target {
// id: number;
// name: string;
// }
// type Target = {
// id: number;
// name: string;
// }
describe("structure match", () => {
const isTarget = (arg: unknown): arg is Target =>
typeof arg === "object" &&
arg !== null &&
typeof (arg as Target).id === "number" &&
typeof (arg as Target).name === "string";
describe("valid", () => {
const validObj = {
id: 1,
name: "taro",
};
const returnTarget = (target: Target): Target => target;
it("instanceof(Target が Class の場合には、コンパイルエラーとなる)", () => {
expect(returnTarget(validObj) instanceof Target).toBeFalsy();
});
it("guard", () => {
expect(isTarget(validObj)).toBeTruthy();
});
});
describe("invalid", () => {
const invalidObj = {
id: true,
name: "fuga",
};
it("instanceof(Target が Class の場合には、コンパイルエラーとなる)", () => {
expect(invalidObj instanceof Target).toBeFalsy();
});
it("guard", () => {
expect(isTarget(invalidObj)).toBeFalsy();
});
});
describe("extended", () => {
const extendedValidObj = {
id: 2,
name: "saburo",
c: "fuga",
};
it("instanceof(Target が Class の場合には、コンパイルエラーとなる)", () => {
expect(extendedValidObj instanceof Target).toBeFalsy();
});
it("guard", () => {
expect(isTarget(extendedValidObj)).toBeTruthy();
});
});
});
});
参考
Discussion