web-dev-qa-db-fra.com

Vérifier si un objet implémente une interface lors de l'exécution avec TypeScript

Je charge un fichier de configuration JSON au moment de l'exécution et utilise une interface pour définir la structure attendue:

interface EngineConfig {
    pathplanner?: PathPlannerConfig;
    debug?: DebugConfig;
    ...
}

interface PathPlannerConfig {
    nbMaxIter?: number;
    nbIterPerChunk?: number;
    heuristic?: string;
}

interface DebugConfig {
    logLevel?: number;
}

...

Cela facilite l'accès aux différentes propriétés puisque je peux utiliser l'auto-complétion, etc.

Question: y at-il un moyen d’utiliser cette déclaration pour vérifier l’exactitude du fichier que je charge? c'est à dire que je n'ai pas de propriétés inattendues?  

31
MasterScrat

Non.

Actuellement, les types ne sont utilisés que pendant le développement et la compilation . Les informations de type ne sont en aucun cas traduites en Code JavaScript.

De https://stackoverflow.com/a/16016688/318557 , comme l'a souligné @JasonEvans

9
MasterScrat

Il y a un moyen, mais vous devez le mettre en œuvre vous-même. Cela s'appelle un "Type défini par l'utilisateur" et cela ressemble à ceci:

interface Test {
    prop: number;
}

function isTest(arg: any): arg is Test {
    return arg && arg.prop && typeof(arg.prop) == 'number';
}

Bien sûr, l’implémentation réelle de la fonction isTest dépend entièrement de vous, mais l’essentiel est qu’il s’agisse d’une fonction réelle, ce qui signifie qu’elle peut être testée. 

Au moment de l'exécution, vous utiliseriez isTest() pour valider si un objet respecte une interface. Au moment de la compilation, TypeScript relève le fil conducteur et traite l’utilisation ultérieure comme prévu, c’est-à-dire:

let a:any = { prop: 5 };

a.x; //ok because here a is of type any

if (isTest(a)) {
    a.x; //error because here a is of type Test
}

Des explications plus détaillées ici: https://basarat.gitbooks.io/TypeScript/content/docs/types/typeGuard.html

12
Mtz

Oui. Vous pouvez effectuer cette vérification au moment de l'exécution en utilisant une version améliorée du compilateur TypeScript que j'ai publié il y a quelques temps. Vous pouvez faire quelque chose comme ce qui suit:

export interface Person {
    name: string;
    surname: string;
    age: number;
}

let personOk = { name: "John", surname: "Doe", age: 36 };
let personNotOk = { name: 22, age: "x" };

// YES. Now you CAN use an interface as a type reference object.
console.log("isValid(personOk):  " + isValid(personOk, Person) + "\n");
console.log("isValid(personNotOk):  " + isValid(personNotOk, Person) + "\n");

et voici la sortie:

isValid(personOk):  true

Field name should be string but it is number
isValid(personNotOk):  false

Veuillez noter que la fonction isValidfonctionne récursivement, de sorte que vous pouvez également l'utiliser pour valider des objets imbriqués. Vous pouvez trouver l'exemple de travail complet ici

6
pcan

Je soupçonne que TypeScript adhère (judicieusement) à la loi de Curly et que TypeScript est un transpiler, pas un validateur d'objet. Cela dit, je pense également que les interfaces TypeScript permettraient une validation d'objet médiocre, car les interfaces ont un vocabulaire (merveilleusement) limité et ne peuvent pas être validées par rapport aux formes que d'autres programmeurs peuvent utiliser pour distinguer les objets, tels que la longueur d'un tableau, le nombre de propriétés, propriétés du motif, etc. 

Lorsque je consomme des objets à partir de code non TypeScript, j'utilise un package JSONSchema validation, tel que AJV , pour la validation au moment de l'exécution, ainsi qu'un générateur de fichier .d.ts (tel que DTSgener ou DTS-generator ) pour compiler les définitions de type TypeScript à partir de mon JSONshcema. 

L’avertissement principal est que, parce que JSONschemata est capable de décrire des formes qui ne peuvent pas être distinguées par TypeScript (telles que patternProperties ), ce n’est pas une traduction individuelle du schéma JSON en .t.ds, et vous pouvez avoir d’éditer manuellement les fichiers .d.ts générés lors de l’utilisation de tels schémas JSON.

Cela dit, comme d'autres programmeurs peuvent utiliser des propriétés telles que la longueur d'un tableau pour déduire le type d'objet, j'ai l'habitude de distinguer les types qui pourraient être confondus par le compilateur TypeScript en utilisant enum pour empêcher le transpiler d'accepter l'utilisation d'un type au lieu du type. autre, comme ça: 

[MyTypes.yaml]

definitions: 
    type-A: 
        type: object
        properties:
            type:
                enum:
                - A
            foo: 
                type: array
                item: string
                maxLength: 2
    type-B: 
        type: object
        properties:
            type:
                enum:
                - B
            foo: 
                type: array
                item: string
                minLength: 3
        items: number

Ce qui génère un fichier .d.ts comme ceci: 

[MyTypes.d.ts]

interface typeA{
    type: "A";
    foo: string[];
}

interface typeB{
    type: "B";
    foo: string[];
}
6
Jthorpe

Voici une autre alternative, spécifiquement pour cela:

ts-interface-builder est un outil que vous exécutez au moment de la construction de votre fichier TypeScript (par exemple, foo.ts) pour générer des descripteurs d'exécution (par exemple, foo-ti.ts).

ts-interface-checker les utilise pour valider des objets au moment de l'exécution. Par exemple.

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

checkers.EngineConfig.check(someObject);   // Succeeds or throws an informative error
checkers.PathPlannerConfig.check(someObject);

Vous pouvez utiliser la méthode strictCheck() pour vous assurer qu'il n'y a pas de propriétés inconnues.

4
DS.

Voici un bon moyen. Vous pouvez convertir une interface TypeScript en schéma JSON à l'aide de TypeScript-json-schema , par exemple.

TypeScript-json-schema --required --noExtraProps \
  -o YOUR_SCHEMA.json YOUR_CODE.ts YOUR_INTERFACE_NAME

Puis validez les données au moment de l’exécution à l’aide d’un validateur de schéma JSON tel que ajv , par exemple.

const fs = require('fs');
const Ajv = require('ajv');

// Load schema
const schema = JSON.parse(fs.readFileSync('YOUR_SCHEMA.json', {encoding:"utf8"}));
const ajv = new Ajv();
ajv.addMetaSchema(require('ajv/lib/refs/json-schema-draft-04.json'));
var validator = ajv.compile(schema);

if (!validator({"hello": "world"})) {
  console.log(validator.errors);
}
3
DS.

oui, il y a une lib qui le fait https://github.com/gcanti/io-ts

l'idée est simple, il suffit de faire des contrôles simples pour les propriétés composées en contrôles plus complexes pour les objets

1
Aleksey Bykov

Je ne sais pas à quoi ressemble votre fichier de configuration, mais le plus évident serait le fichier json, bien que j'irais avec le schéma json pour valider si le fichier correspond au schéma ou non.

Voici la documentation de json schema v4: http://json-schema.org/documentation.html

Et un exemple de test: https://github.com/fge/json-schema-validator

Bien sûr, vous devez écrire votre schéma en fonction d'interfaces, mais vous ne pouvez pas les utiliser directement.

1
Maciej Kwas

Vous pouvez utiliser class-validation

  1. Remplacer l'interface pour la classe.

 classe Cat {
 @IsNotEmpty () name: string; 
 } 

 // Le typage statique est un travail !!! 
 const cat: Cat = {
 nom: "Barsik" 
 }; 

  1. Créer une fonction de validation. Exemple:

 importer {validateSync} de "class-validator"; 

 type data = {
 [key: string]: any; 
 }; 

 // Créer une nouvelle instance de classe, la remplir et la valider via "class-validator" 
 export const validate = <D étend les données, C étend {new (): D}> 
 (data: D, classTemplate: C): boolean => {
 const instanceClass = new classTemplate (); 
 Object.keys (data) .forEach ((key) => {
 InstanceClass [clé] = data [clé]; 
}); return! validateSync (instanceClass) .length; 
 } 

  1. Utiliser la classe comme interface pour le typage statique et la classe pour la validation

 si (valider (chat, chat)) {
 // D'ACCORD
 } autre {
 // ERREUR
 } 

0