Javascript : Comment valider une instance de classe ES6 au runtime

Quand le Typescript ne suffit plus !

Article publié le 18/11/2020, dernière mise à jour le 19/09/2023

L'intégrité des données est très importante en Javascript, le langage étant dynamiquement typé, une donnée n'ayant pas le bon type ou étant absente peut causer l'arrêt du programme, ou pire, le programme s'exécutera mais pas de la manière prévue.

Et là, je sais ce que vous pensez : "Mais c'est pour ça qu'on utilise Typescript !"

Et je vous répondrai :

Oui, mais cela ne résout pas tous les problèmes, loin de là.

Si vous voulez comprendre en détails pourquoi, je vous invite à lire mon article d'introduction à TypeScript.

Mais en résumé, Typescript permet "simplement" d'éviter des erreurs de typage lors de la phase de développement du programme, mais pour toutes les données qui n'existe dans le programme que lors de l'exécution, comme par exemple les imports de fichiers (csv,...), le localStorage ou encore le contenu de requêtes http, alors TypeScript ne suffit plus !

Pour plus d'informations, je vous invite à lire cet article sur le blog d'Oreilly.

typestack/class-validator

Après quelques recherches et plusieurs tests, j'ai finalement trouvé la bibliothèque "class-validator" qui permet tout simplement de valider le contenu d'un objet, instancié depuis une classe.

Pour ceux qui se poseraient la question sur la fiabilité de cette bibliothèque, "class-validator" est utilisé en interne par NestJS pour son mécanisme de DTO.

Pour l'utiliser il vous suffira de faire un "npm install --save class-validator"

Le fonctionnement

Pour préparer une classe à la validation, "class-validator" fournie un ensemble de décorateurs à ajouter au-dessus de chaque attribut de la classe, comme dans l'exemple suivant :

A noter que tous les exemples de cet article sont écrits en Typescript, bien évidemment la bibliothèque peut être importée en Javascript classique.

//index.ts
import { IsString, IsInt, IsOptional, validate } from 'class-validator';

class Person {
    
    @IsString()
    firstname: string

    @IsString()
    lastname:string

    @IsInt()
    @IsOptional()
    age: number

    constructor(csv_arr: string[]){
        this.firstname = csv_arr[0];
        this.lastname = csv_arr[1];
        this.age = csv_arr[2] ? parseInt(csv_arr[2]) : undefined;
    }
}

Ici j'ai pris l'exemple d'un fichier csv que l'on importerait, on initialise donc le constructeur avec un simple tableau de chaînes de caractères (une ligne du fichier .csv).

Ensuite, il va nous suffire d**'instancier toutes nos personnes à partir des données issues du fichier et d'appeler la méthode de validation.**

A noter que pour simplifier l'exemple, le fichier a été remplacé par un tableau statique, si vous voulez savoir comment importer un fichier csv, j'ai écrit un article détaillé sur le sujet.

//index.ts
let csv : any[][] = [
    ["Marc", "Devier", 40],
    ["Alice", "Boran",],
    ["Jean",,52]
]

let persons: Person[] = []; // Contains only valid persons

for(let i:number = 0 ; i<csv.length ; i++){

    let person: Person = new Person(csv[i]);
    
    validate(person).then(errors => {
        if(errors.length === 0){
            persons.push(person);
            console.log(person);
        } else {
            console.error(errors);
        }
    });

}

Lors de l'exécution de ce programme, notre tableau "persons" ne contiendra donc que des instances de "Person" valides, l'intégrité des données est donc respectée.

Voici donc les informations loggées lors du déroulement du programme :

Person { firstname: 'Marc', lastname: 'Devier', age: 40 }
Person { firstname: 'Alice', lastname: 'Boran', age: undefined }
[
  ValidationError {
    target: Person { firstname: 'Jean', lastname: undefined, age: 52 },
    value: undefined,
    property: 'lastname',
    children: [],
    constraints: { isString: 'lastname must be a string' }
  }
]

On y voit bien que notre deuxième objet a été validé, même si son age n'est pas défini car il a été déclaré comme optionnel, mais qu'au contraire notre troisième objet n'est pas valide car il manque son nom de famille.

Il existe énormément de décorateurs différents, non pas simplement sur le type de donnée mais aussi le contenu de cette dernière (on peut valider la taille d'une string, ou encore vérifier que c'est une adresse email par exemple).

Pour plus d'informations je vous invite à consulter la documentation directement sur Github : https://github.com/typestack/class-validator

Alternative

Bien sûr il existe d'autres alternatives, comme JOI par exemple, qui vous permettra non plus de valider une instance de classe, mais n'importe quel objet JSON !

J'espère que cet article vous aura plus, et à bientôt sur le blog !


Vous avez terminé l'article ?

Commentaires (0)

pour laisser un commentaire

Aucun commentaire pour l'instant