670 Stimmen

Schnittstellentypprüfung mit Typescript

Diese Frage ist das direkte Analogon zu Klassentypprüfung mit TypeScript

Ich muss zur Laufzeit herausfinden, ob eine Variable vom Typ any eine Schnittstelle implementiert. Hier ist mein Code:

interface A{
    member:string;
}

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

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

Wenn Sie diesen Code in den Typescript-Spielplatz eingeben, wird die letzte Zeile als Fehler markiert: "Der Name A existiert nicht im aktuellen Bereich". Aber das ist nicht wahr, der Name existiert im aktuellen Bereich. Ich kann sogar die Variablendeklaration ändern in var a:A={member:"foobar"}; ohne Beanstandungen seitens des Herausgebers. Nachdem ich das Web durchsucht und die andere Frage zu SO gefunden hatte, änderte ich die Schnittstelle in eine Klasse, aber dann kann ich keine Objektliterale verwenden, um Instanzen zu erstellen.

Ich fragte mich, wie der Typ A auf diese Weise verschwinden konnte, aber ein Blick auf das generierte Javascript erklärt das Problem:

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

Es gibt keine Repräsentation von A als Schnittstelle, daher sind keine Laufzeittypprüfungen möglich.

Ich verstehe, dass Javascript als dynamische Sprache kein Konzept von Schnittstellen hat. Gibt es eine Möglichkeit, Typprüfung für Schnittstellen?

Die Autovervollständigung des typescript-Spielplatzes zeigt, dass typescript sogar eine Methode anbietet implements . Wie kann ich es verwenden?

-2voto

ChrisW Punkte 53239

Da der Typ zur Laufzeit nicht bekannt ist, habe ich den Code wie folgt geschrieben, um das unbekannte Objekt nicht mit einem Typ, sondern mit einem Objekt bekannten Typs zu vergleichen:

  1. Erstellen Sie ein Musterobjekt des richtigen Typs
  2. Geben Sie an, welche seiner Elemente optional sind
  3. Führen Sie einen Tiefenvergleich Ihres unbekannten Objekts mit diesem Beispielobjekt durch

Hier ist der (schnittstellenunabhängige) Code, den ich für den tiefen Vergleich verwende:

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

Nachfolgend ein Beispiel, wie ich es verwende.

In diesem Beispiel erwarte ich, dass das JSON ein Array von Tupeln enthält, von denen das zweite Element eine Instanz einer Schnittstelle namens User (der zwei fakultative Elemente hat).

Die Typüberprüfung von TypeScript stellt sicher, dass mein Beispielobjekt korrekt ist, und die Funktion assertTypeT prüft dann, ob das unbekannte (aus JSON geladene) Objekt mit dem Beispielobjekt übereinstimmt.

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

Sie könnten eine solche Prüfung in der Implementierung eines benutzerdefinierten Type Guard aufrufen.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X