Typescriptによるインターフェース型チェック


292

この質問は、TypeScriptを使用したクラス型チェックに直接類似しています

タイプの変数がインターフェイスを実装しているかどうかを実行時に確認する必要があります。これが私のコードです:

interface A{
    member:string;
}

var a:any={member:"foobar"};

if(a instanceof A) alert(a.member);

typescript playgroundにこのコードを入力すると、最後の行は「名前Aは現在のスコープに存在しません」というエラーとしてマークされます。しかし、そうではありません。名前は現在のスコープに存在します。var a:A={member:"foobar"};エディターからの不満なしに変数宣言を変更することもできます。Webを閲覧してSOで他の質問を見つけた後、インターフェイスをクラスに変更しましたが、オブジェクトリテラルを使用してインスタンスを作成できません。

タイプAがどのようにしてこのように消えてしまうのか疑問に思いましたが、生成されたJavaScriptを見ると問題が説明されています。

var a = {
    member: "foobar"
};
if(a instanceof A) {
    alert(a.member);
}

Aはインターフェースとして表現されないため、実行時の型チェックはできません。

動的言語としてのJavaScriptにはインターフェースの概念がないことを理解しています。インターフェイスのタイプチェックを行う方法はありますか?

typescript playgroundのオートコンプリートは、typescriptがメソッドを提供することさえ明らかにしますimplements。どうすれば使用できますか?


4
JavaScriptにはインターフェースの概念はありませんが、それは動的言語であるためではありません。これは、インターフェースがまだ実装されていないためです。
trusktr 2017

はい。ただし、インターフェースの代わりにクラスを使用できます。この例を参照してください。
Alexey Baranoshnikov

どうやら2017年にはありません。今、超関連の質問。
doublejosh

回答:


220

instanceofカスタムタイプガードを今すぐ作成できるので、キーワードなしで必要なことを実現できます。

interface A{
    member:string;
}

function instanceOfA(object: any): object is A {
    return 'member' in object;
}

var a:any={member:"foobar"};

if (instanceOfA(a)) {
    alert(a.member);
}

たくさんのメンバー

多くのメンバーをチェックして、オブジェクトがあなたのタイプと一致するかどうかを判断する必要がある場合は、代わりに弁別子を​​追加できます。以下は最も基本的な例であり、独自の弁別器を管理する必要があります...弁別器の重複を避けるために、パターンをより深く理解する必要があります。

interface A{
    discriminator: 'I-AM-A';
    member:string;
}

function instanceOfA(object: any): object is A {
    return object.discriminator === 'I-AM-A';
}

var a:any = {discriminator: 'I-AM-A', member:"foobar"};

if (instanceOfA(a)) {
    alert(a.member);
}

85
「インターフェースをランタイムチェックする方法はありません。」なんらかの理由でまだ実装されていません。
trusktr 2017

16
インターフェイスに100のメンバーがある場合、100をすべてチェックする必要がありますか?フーバー。
Jenny O'Reilly

4
100をすべてチェックするのではなく、オブジェクトに弁別子を​​追加することができます...
Fenton

7
このディスクリミネーターパラダイム(ここに記述)は、拡張インターフェイスをサポートしていません。派生インターフェースは、それが基本インターフェースのinstanceOfであるかどうかを確認するとfalseを返します。
アーロン

1
@Fentonおそらく私はこれについて十分に理解していませんが、インターフェースAを拡張するインターフェースBがあるとすると、isInstanceOfA(instantiatedB)true isInstanceOfB(instantiatedA)を返したいがfalseを返したいとしましょう。後者が発生するためには、Bの弁別子が「I-AM-A」である必要はありませんか?
アーロン

86

TypeScript 1.6では、ユーザー定義のタイプガードがその役割を果たします。

interface Foo {
    fooProperty: string;
}

interface Bar {
    barProperty: string;
}

function isFoo(object: any): object is Foo {
    return 'fooProperty' in object;
}

let object: Foo | Bar;

if (isFoo(object)) {
    // `object` has type `Foo`.
    object.fooProperty;
} else {
    // `object` has type `Bar`.
    object.barProperty;
}

そして、Joe Yangが述べたように:TypeScript 2.0以降、タグ付き共用体型を利用することもできます。

interface Foo {
    type: 'foo';
    fooProperty: string;
}

interface Bar {
    type: 'bar';
    barProperty: number;
}

let object: Foo | Bar;

// You will see errors if `strictNullChecks` is enabled.
if (object.type === 'foo') {
    // object has type `Foo`.
    object.fooProperty;
} else {
    // object has type `Bar`.
    object.barProperty;
}

そしてそれも動作しswitchます。


1
これは奇妙に見えます。どうやら利用可能なメタ情報のようなものがあります。なぜこのタイプガード構文で公開するのか。isinstanceofとは対照的に、関数の動作の隣にある「オブジェクトはインターフェイス」という制約があるためです。より正確には、ifステートメントで「オブジェクトはインターフェイス」を直接使用できますか?しかし、いずれにせよ、非常に興味深い構文、私からの+1。
lhk

1
@lhkいいえ、そのようなステートメントはありません。これは、条件付きブランチ内で型を狭める方法を指示する特別な型のようなものです。TypeScriptの「スコープ」により、将来的にはそのような発言はないと思います。別の間で異なるobject is typeobject instanceof class、プレーンオブジェクトまたはクラスのインスタンス、それは問題ではない:活字体で、つまりタイプは構造的である、それは代わりに、オブジェクトから形状を手に入れた場所でのみ「形状」を気遣います。
vilicvane

2
誤解を解消するためだけに、この回答は作成する可能性があります。実行時にオブジェクトタイプまたはそのインターフェイスを差し引くメタ情報はありません。
mostruash 2016年

@mostruashうん、答えの後半はコンパイルしても実行時に機能しません。
trusktr 2017

4
ああ、しかし、これは実行時にこれらのオブジェクトがtypeプロパティで作成されていると想定しなければなりません。その場合は動作します。その例はこの事実を示していません。
trusktr 2017

40

typescript 2.0はタグ付きユニオンを導入します

Typescript 2.0の機能

interface Square {
    kind: "square";
    size: number;
}

interface Rectangle {
    kind: "rectangle";
    width: number;
    height: number;
}

interface Circle {
    kind: "circle";
    radius: number;
}

type Shape = Square | Rectangle | Circle;

function area(s: Shape) {
    // In the following switch statement, the type of s is narrowed in each case clause
    // according to the value of the discriminant property, thus allowing the other properties
    // of that variant to be accessed without a type assertion.
    switch (s.kind) {
        case "square": return s.size * s.size;
        case "rectangle": return s.width * s.height;
        case "circle": return Math.PI * s.radius * s.radius;
    }
}

2.0ベータ版を使用していますが、タグ付きユニオンが機能しません。<TypeScriptToolsVersion> 2.0 </ TypeScriptToolsVersion>
Makla

ナイトリービルドでコンパイルされていますが、インテリセンスは機能しません。また、エラーも一覧表示されます。プロパティの幅/サイズ/ ...がタイプ 'Square | 長方形| ケースステートメントの円。しかし、コンパイルされます。
Makla

23
これは本当に弁別子を​​使っているだけです。
エリックフィリップス

32

ユーザー定義のタイプガードはどうですか?https://www.typescriptlang.org/docs/handbook/advanced-types.html

interface Bird {
    fly();
    layEggs();
}

interface Fish {
    swim();
    layEggs();
}

function isFish(pet: Fish | Bird): pet is Fish { //magic happens here
    return (<Fish>pet).swim !== undefined;
}

// Both calls to 'swim' and 'fly' are now okay.

if (isFish(pet)) {
    pet.swim();
}
else {
    pet.fly();
}

3
これは私のお気に入りの答えです-stackoverflow.com/a/33733258/469777に似ていますが、縮小などの理由で壊れる可能性がある魔法の文字列はありません。
スタッフォードウィリアムズ

1
これは私には何らかの理由で(pet as Fish).swim !== undefined;うまくいきませんでしたが、うまくいきました。
Cyber​​Mew

18

それが可能になりました、私は拡張バージョンをリリースしました TypeScript完全なリフレクション機能を提供コンパイラの。メタデータオブジェクトからクラスをインスタンス化し、クラスコンストラクターからメタデータを取得して、実行時にインターフェイス/クラスを検査できます。こちらでチェックできます

使用例:

タイプスクリプトファイルの1つで、次のように実装するインターフェイスとクラスを作成します。

interface MyInterface {
    doSomething(what: string): number;
}

class MyClass implements MyInterface {
    counter = 0;

    doSomething(what: string): number {
        console.log('Doing ' + what);
        return this.counter++;
    }
}

次に、実装されたインターフェースのリストを出力します。

for (let classInterface of MyClass.getClass().implements) {
    console.log('Implemented interface: ' + classInterface.name)
}

reflec-tsでコンパイルして起動します。

$ node main.js
Implemented interface: MyInterface
Member name: counter - member kind: number
Member name: doSomething - member kind: function

リフレクション.d.tsを参照してください Interfaceメタタイプの詳細については、ください。

更新:ここで 完全に機能する例を見つけることができます


8
私はこれが愚かだと思ったcosに反対票を投じましたが、しばらく一時停止し、githubページを見て、最新の状態に保たれていることを確認し、代わりに十分に文書化されていることを確認しました。implementsしかし、あなたのコミットメントを認識したくて、意地悪になりたくありませんでした:-)
Simon_Weaver

5
実際、このリフレクション機能の主な目的は、Javaの世界に古くから存在しているような優れたIoCフレームワークを作成することです(Springが最初で最も重要なものです)。TypeScriptは将来の最高の開発ツールの1つになる可能性があり、リフレクションは本当に必要な機能の1つであると確信しています。
pcan

5
...ええと、では、これらのコンパイラの "拡張機能"をTypescriptの今後のビルドに組み込む必要があるのでしょうか。これは事実上、Typescript自体ではなく、Typescriptのフォークです。その場合、これは実行可能な長期的なソリューションではありません。
dudewad 16

1
@dudewad他の多くのトピックで述べたように、これは一時的な解決策です。トランスフォーマーによるコンパイラーの拡張性を待っています。TypeScript公式リポジトリの関連する問題をご覧ください。さらに、広く採用されているすべての強い型付け言語にはリフレクションがあり、TypeScriptにもそれがあるはずだと思います。そして、私のように、他の多くのユーザーもそう思っています。
pcan 2016

ええ、私が同意しないというわけではありません-これも欲しいです。ただ、カスタムコンパイラを起動することは、Typescriptの次のパッチを移植する必要があることを意味しませんか?あなたがそれを維持しているなら、賞賛します。大変な作業のようです。それをノックしていません。
dudewad 2016

9

ユーザー定義のガードが使用れた上記と同じですが、今回はアロー関数述語を使用します

interface A {
  member:string;
}

const check = (p: any): p is A => p.hasOwnProperty('member');

var foo: any = { member: "foobar" };
if (check(foo))
    alert(foo.member);

8

別のオプションを次に示します。モジュールts-interface-builderは、TypeScriptインターフェースをランタイム記述子に変換するビルド時ツールを提供し、ts-interface-checkerは、オブジェクトがそれを満たしているかどうかをチェックできます。

OPの例では、

interface A {
  member: string;
}

最初に実行ts-interface-builderして、次のfoo-ti.tsように使用できる記述子などの新しい簡潔なファイルを生成します。

import fooDesc from './foo-ti.ts';
import {createCheckers} from "ts-interface-checker";
const {A} = createCheckers(fooDesc);

A.check({member: "hello"});           // OK
A.check({member: 17});                // Fails with ".member is not a string" 

ワンライナータイプガード関数を作成できます:

function isA(value: any): value is A { return A.test(value); }

6

TypeScriptは、オブジェクトが特定のインターフェイスを実装しているかどうかを動的にテストするための直接的なメカニズムを提供していないことを指摘しておきます。

代わりに、TypeScriptコードは、JavaScriptテクニックを使用して、適切なメンバーのセットがオブジェクトに存在するかどうかをチェックできます。例えば:

var obj : any = new Foo();

if (obj.someInterfaceMethod) {
    ...
}

4
複雑な形状がある場合はどうなりますか?深さの各レベルですべての単一のプロパティをハードコードしたくない場合
Tom

@Tom実行時の値または例/エグザンプラ(つまり、必要なインターフェースのオブジェクト)を(チェッカー関数の2番目のパラメーターとして)渡すことができると思います。次に、コードをハードコーディングする代わりに、必要なインターフェイスの例を記述for (element in obj) {}し、2回のオブジェクトに同様のタイプの同様の要素があることを確認するために、(egを使用して)一度オブジェクト比較コードを記述します。
ChrisW

4

TypeGuards

interface MyInterfaced {
    x: number
}

function isMyInterfaced(arg: any): arg is MyInterfaced {
    return arg.x !== undefined;
}

if (isMyInterfaced(obj)) {
    (obj as MyInterfaced ).x;
}

2
「arg is MyInterfaced」は興味深い注釈です。それが失敗した場合はどうなりますか?コンパイル時のインターフェースチェックのように見えます-そもそも私が欲しかったものです。しかし、コンパイラーがパラメーターをチェックするのであれば、なぜ関数本体があるのでしょうか。そして、そのようなチェックが可能な場合は、なぜそれを別の関数に移動するのか。
lhk 2017年

1
@lhkはタイプガードに関するtypescriptドキュメントを読んでください... typescriptlang.org/docs/handbook/advanced-types.html
Dmitry Matveev

3

Fentonの答えに基づいて、ここに、指定されobjectたキーinterfaceが完全にまたは部分的に持っているかどうかを確認する関数の実装を示します。

ユースケースによっては、各インターフェースのプロパティのタイプを確認する必要がある場合もあります。以下のコードはそれを行いません。

function implementsTKeys<T>(obj: any, keys: (keyof T)[]): obj is T {
    if (!obj || !Array.isArray(keys)) {
        return false;
    }

    const implementKeys = keys.reduce((impl, key) => impl && key in obj, true);

    return implementKeys;
}

使用例:

interface A {
    propOfA: string;
    methodOfA: Function;
}

let objectA: any = { propOfA: '' };

// Check if objectA partially implements A
let implementsA = implementsTKeys<A>(objectA, ['propOfA']);

console.log(implementsA); // true

objectA.methodOfA = () => true;

// Check if objectA fully implements A
implementsA = implementsTKeys<A>(objectA, ['propOfA', 'methodOfA']);

console.log(implementsA); // true

objectA = {};

// Check again if objectA fully implements A
implementsA = implementsTKeys<A>(objectA, ['propOfA', 'methodOfA']);

console.log(implementsA); // false, as objectA now is an empty object

2
export interface ConfSteps {
    group: string;
    key: string;
    steps: string[];
}
private verify(): void {
    const obj = `{
      "group": "group",
      "key": "key",
      "steps": [],
      "stepsPlus": []
    } `;
    if (this.implementsObject<ConfSteps>(obj, ['group', 'key', 'steps'])) {
      console.log(`Implements ConfSteps: ${obj}`);
    }
  }
private objProperties: Array<string> = [];

private implementsObject<T>(obj: any, keys: (keyof T)[]): boolean {
    JSON.parse(JSON.stringify(obj), (key, value) => {
      this.objProperties.push(key);
    });
    for (const key of keys) {
      if (!this.objProperties.includes(key.toString())) {
        return false;
      }
    }
    this.objProperties = null;
    return true;
  }

1
このコードは質問に答えることがありますが、このコードが質問に答える理由や方法に関する追加のコンテキストを提供すると、長期的な価値が向上します。
xiawi

0

実行時に型が不明なため、不明なオブジェクトを型ではなく、既知の型のオブジェクトと比較するコードを次のように記述しました。

  1. 適切なタイプのサンプルオブジェクトを作成する
  2. オプションの要素を指定します
  3. 未知のオブジェクトをこのサンプルオブジェクトと比較します

以下は、深い比較に使用する(インターフェースに依存しない)コードです。

function assertTypeT<T>(loaded: any, wanted: T, optional?: Set<string>): T {
  // this is called recursively to compare each element
  function assertType(found: any, wanted: any, keyNames?: string): void {
    if (typeof wanted !== typeof found) {
      throw new Error(`assertType expected ${typeof wanted} but found ${typeof found}`);
    }
    switch (typeof wanted) {
      case "boolean":
      case "number":
      case "string":
        return; // primitive value type -- done checking
      case "object":
        break; // more to check
      case "undefined":
      case "symbol":
      case "function":
      default:
        throw new Error(`assertType does not support ${typeof wanted}`);
    }
    if (Array.isArray(wanted)) {
      if (!Array.isArray(found)) {
        throw new Error(`assertType expected an array but found ${found}`);
      }
      if (wanted.length === 1) {
        // assume we want a homogenous array with all elements the same type
        for (const element of found) {
          assertType(element, wanted[0]);
        }
      } else {
        // assume we want a tuple
        if (found.length !== wanted.length) {
          throw new Error(
            `assertType expected tuple length ${wanted.length} found ${found.length}`);
        }
        for (let i = 0; i < wanted.length; ++i) {
          assertType(found[i], wanted[i]);
        }
      }
      return;
    }
    for (const key in wanted) {
      const expectedKey = keyNames ? keyNames + "." + key : key;
      if (typeof found[key] === 'undefined') {
        if (!optional || !optional.has(expectedKey)) {
          throw new Error(`assertType expected key ${expectedKey}`);
        }
      } else {
        assertType(found[key], wanted[key], expectedKey);
      }
    }
  }

  assertType(loaded, wanted);
  return loaded as T;
}

以下は、それを使用する方法の例です。

この例では、JSONにタプルの配列が含まれていることを期待しています。その2番目の要素は、呼び出されたインターフェースのインスタンスですUser(2つのオプション要素があります)。

TypeScriptの型チェックは、サンプルオブジェクトが正しいことを確認し、assertTypeT関数は、不明な(JSONからロードされた)オブジェクトがサンプルオブジェクトと一致することをチェックします。

export function loadUsers(): Map<number, User> {
  const found = require("./users.json");
  const sample: [number, User] = [
    49942,
    {
      "name": "ChrisW",
      "email": "example@example.com",
      "gravatarHash": "75bfdecf63c3495489123fe9c0b833e1",
      "profile": {
        "location": "Normandy",
        "aboutMe": "I wrote this!\n\nFurther details are to be supplied ..."
      },
      "favourites": []
    }
  ];
  const optional: Set<string> = new Set<string>(["profile.aboutMe", "profile.location"]);
  const loaded: [number, User][] = assertTypeT(found, [sample], optional);
  return new Map<number, User>(loaded);
}

ユーザー定義の型ガードの実装で、このようなチェックを呼び出すことができます。


0

実行時にTypeScriptタイプを検証できます。 ようにts-validate-type(ただし、Babelプラグインが必要です)。

const user = validateType<{ name: string }>(data);
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.