Validation d'objets JavaScript à l'aide de schéma JSON et d'AJV

La validation d’objets (à savoir la vérification qu’un objet respecte un schéma prédéfini) en JavaScript et TypeScript est un besoin fréquent, et il existe déjà une multitude de solutions à cette dernière. Dans cet article, nous étudierons le package Node.js AJV, qui permet de valider des objets à l’aide de schémas JSON. La librairie étant extrêmement riche, nous ne survolerons dans cet article que les principales fonctionnalités.

Nous utiliserons TypeScript dans cet article mais la librairie fonctionne très bien en JavaScript. Le package AJV est livré par défaut avec ses types TypeScript, ce qui signifie que la commande npm install ajv --save suffit à installer le package et ses types d’un seul coup.

Création de Schémas JSON pour vos validations d’objets

Lorsque l’on travaille avec des fichiers JSON que l’on reçoit en entrée (ou que l’on génère en sortie), il est parfois nécessaire de définir des Schémas (en suivant la norme JSON Schema) afin de pouvoir vérifier la validité des structures en question. Dans le cas où ces structures sont utilisées en tant qu’input du programme, il sera plus facile de détecter et d’isoler les incohérences des données d’entrée. Ces incohérences correspondent essentiellement à des champs des objets qui sont manquants, en trop ou qui ne possèdent pas le bon type.

Il existe plusieurs versions de Draft de JSON Schema. La version la plus utilisée aujourd’hui est la JSON Schema Draft-07. AJV supporte toutes les versions de Drafts existants actuellement, mais il existe des nuances sur l’installation et l’import du package selon les versions de Draft utilisées. Ces nuances sont détaillées dans la documentation officielle d’AJV.

Import du Schéma JSON

Le principe d’AJV est simple. Il est possible de lui donner en entrée un schéma JSON, puis la librairie se charge de compiler ce dernier, et de générer une fonction de validation qui lui correspond. Cette fonction pourra par la suite être utilisée pour valider des objets.

La première étape est donc d’importer un schéma JSON. Il est possible d’effectuer cela de deux manières. Dans le cas de l’import de fichiers fixes (on cherche par exemple à charger un fichier depuis le système via son chemin) : le schéma peut être chargé comme un module, de la manière suivante :

import * as schema from 'path/to/my/schema.json';

À noter qu’il faudra spécifier la propriété resolveJsonModule: true dans le tsconfig.json afin d’activer l’import de fichiers JSON.

Si, au contraire, on souhaite charger dynamiquement le schéma (le programme cherche par exemple à charger des schémas à la volée), on peut parser manuellement le schéma avec la méthode suivante :

import fs from 'fs';

const schema = JSON.parse(fs.readFileSync('path/to/my/schema.json', 'utf8')) as unknown;

À noter qu’ajouter le cast en unknown après l’appel à JSON.parse() est une bonne pratique car le type parsé est inconnu (plutôt que any). Le fait que beaucoup de méthodes proposées par défaut retournent any est dû au fait que le type unknown n’existait pas lors de l’écriture des fichiers de définition.

Une fois le schéma JSON importé, il est possible de générer une fonction de validation de la manière suivante :

import Ajv from 'ajv';

const ajv = new Ajv();
const validate = ajv.compile(schema);

Validation des objets

La fonction de validation générée prend en entrée l’objet à valider, et renvoie un booléen indiquant si l’objet est valide ou non. Dans le cas où l’objet n’est pas valide, la fonction (en tant qu’objet JS) contiendra un champ errors indiquant les champs manquants ou invalides de l’objet :

if (!validate(myObject)) {
  console.log(validate.errors);
}
/*
 * Output :
 * [{
 *   instancePath: '/myField',
 *   schemaPath: '#/properties/myField/type',
 *   keyword: 'type',
 *   params: { type: 'string' },
 *   message: 'must be string'
 * }]
 */

À noter que si l’on spécifie un message d’erreur custom dans le JSON Schéma, ce dernier sera utilisé comme message dans l’objet error renvoyé.

Création de schémas de validation en JavaScript

On peut remarquer que la méthode ajv.compile() prend en argument le schéma JSON, parsé en objet JavaScript. Cela signifie qu’il est aussi possible de définir des schémas directement en tant qu’objets dans le code. Ce procédé peut être utile dans le cas où l’on souhaite créer des règles de validation dynamiques pour nos objets. Voici un exemple de schéma dynamique :

const schema = {
  type: 'object',
  properties: {
    myField: { type: 'integer' },
    myOtherField: { type: 'string' },
  },
  required: [ 'foo' ],
  additionalProperties: false,
};

const validate = ajv.compile(schema);

Le compilateur AJV propose aussi des méthodes de validation additionnelles qui sont absentes de la norme JSON Schéma, comme par exemple l’utilisation du mot clé $data, qui fait référence à des valeurs de propriétés présentes dans le fichier JSON. En effet, à l’aide d’AJV, il est possible de définir un schéma dans lequel les propriétés foo et bar doivent être égales de la manière suivante :

const schema = {
  type: 'object',
  properties: {
    foo: { type: 'string' },
    bar: { const: { $data: '1/foo' } },
  },
};

Ces fonctionnalités de validations supplémentaires sont puissantes, mais à utiliser avec précaution car elles induisent un découplage entre nos règles de validation et la norme JSON Schéma. En les utilisant, on base notre programme sur des règles spécifiques à AJV, et on crée par conséquent une forte dépendance entre notre logique métier et la librairie de validation.

Conclusion

La librairie AJV est une librairie permettant de valider des objets JavaScript à l’aide de schémas JSON. Elle propose un compilateur de schéma générant des fonctions de validation pour chaque schéma. Elle propose aussi des fonctionnalités supplémentaires, non présentes dans la norme JSON Schéma officielle, qui sont très pratiques pour définir des règles de validation plus poussées.

Dans cet article, nous n’avons que survolé les fonctionnalités de base de la librairie, mais je vous invite fortement à parcourir leur documentation officielle, qui est très complète.