Проверка типа интерфейса с помощью Typescript

Этот вопрос является прямым аналогом Проверка типа класса с помощью TypeScript

Мне нужно узнать во время выполнения, если переменная типа any реализует интерфейс. Здесь мой код:

interface A{
    member:string;
}

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

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

Если вы введете этот код на игровой площадке typescript, последняя строка будет отмечена как ошибка, "Имя A не существует в текущей области". Но это не так, это имя существует в текущей области. Я даже могу изменить объявление переменной на var a:A={member:"foobar"}; без жалоб редактора. После просмотра веб-страницы и поиска другого вопроса на SO я изменил интерфейс на класс, но затем я не могу использовать литералы объектов для создания экземпляров.

Я задавался вопросом, как тип A может исчезнуть, но посмотрите на сгенерированный javascript, который объясняет проблему:

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

Отсутствует представление A как интерфейса, поэтому проверки типа времени выполнения не возможны.

Я понимаю, что javascript как динамический язык не имеет понятия интерфейсов. Есть ли способ ввести проверку интерфейсов?

Автозаполнение игровой площадки typescript показывает, что typescript даже предлагает метод implements. Как я могу использовать его?

Ответ 1

Вы можете достичь того, чего хотите, без ключевого слова 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);
}

Ответ 2

В 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;
}

И так же, как сказал Джо Ян: с TypeScript 2.0 вы даже можете воспользоваться тегом типа union.

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.

Ответ 3

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;
    }
}

Ответ 4

Как насчет пользовательских гвардейцев типов? 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();
}

Ответ 5

Теперь это возможно, я только что выпустил улучшенную версию компилятора TypeScript которая предоставляет возможности полного отражения. Вы можете создавать экземпляры классов из их объектов метаданных, извлекать метаданные из конструкторов классов и проверять интерфейс/классы во время выполнения. Вы можете проверить это здесь

Пример использования:

В одном из ваших файлов машинописи создайте интерфейс и класс, который реализует его следующим образом:

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)
}

скомпилируйте с отражением и запустите его:

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

См. Отражение .d.ts для мета-типа Interface.

ОБНОВЛЕНИЕ: Вы можете найти полный рабочий пример здесь

Ответ 6

Здесь другая опция: модуль ts-interface-builder предоставляет инструмент времени сборки, который преобразует интерфейс TypeScript в дескриптор времени выполнения, а ts-interface-checker может проверить, удовлетворяет ли объект этому.

Например, OP

interface A {
  member: string;
}

Сначала вы запустите ts-interface-builder который foo-ti.ts новый краткий файл с дескриптором, скажем, 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); }

Ответ 7

Я хотел бы указать, что TypeScript не предоставляет прямого механизма для динамического тестирования того, реализует ли объект определенный интерфейс.

Вместо этого, код TypeScript может использовать метод JavaScript для проверки наличия соответствующего объекта на объекте. Например:

var obj : any = new Foo();

if (obj.someInterfaceMethod) {
    ...
}

Ответ 9

TypeGuards

interface MyInterfaced {
    x: number
}

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

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

Ответ 10

Это сработало для меня w/ TypeScript 2.4.1

interface A{
    member:string;
}

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

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

Ответ 11

Поскольку тип неизвестен во время выполнения, я написал следующий код для сравнения неизвестного объекта не с типом, а с объектом известного типа:

  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 содержит массив кортежей, второй элемент которого является экземпляром интерфейса с именем User (который имеет два необязательных элемента).

Проверка типов в TypeScript обеспечит корректность моего образца объекта, а затем функция assertTypeT проверяет, соответствует ли неизвестный (загруженный из JSON) объект образцу объекта.

export function loadUsers(): Map<number, User> {
  const found = require("./users.json");
  const sample: [number, User] = [
    49942,
    {
      "name": "ChrisW",
      "email": "[email protected]",
      "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);
}

Вы можете вызвать проверку, подобную этой, в реализации определенного пользователем типа guard.

Ответ 12

Основываясь на ответе 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