Сериализация скомпилированных схем

В библиотеке Ajv схема JSON Schema не используется напрямую во время валидации. Перед проверкой данных она проходит этап компиляции, в результате которого создаётся специализированная функция-валидатор.

Компиляция схемы превращает декларативное описание правил в исполняемый JavaScript-код. Такой подход обеспечивает высокую производительность за счёт:

  • устранения интерпретации схемы во время каждой проверки;
  • генерации оптимизированных условных конструкций;
  • минимизации накладных расходов на обход структуры схемы.

Скомпилированная схема в Ajv — это функция, содержащая встроенную логику валидации, а также дополнительные свойства (например, ссылки на исходную схему, параметры формата и метаданные).


Природа сериализации скомпилированных схем

Под сериализацией скомпилированной схемы понимается преобразование результата ajv.compile(schema) в форму, которую можно:

  • сохранить на диск;
  • встроить в сборку приложения;
  • передать между средами выполнения;
  • восстановить без повторной компиляции.

Ключевая особенность: стандартная функция JavaScript не предназначена для надёжной сериализации через JSON.stringify, поскольку:

  • функции теряют замыкания;
  • контекст выполнения не восстанавливается;
  • внутренние ссылки Ajv не сериализуются автоматически.

Поэтому в Ajv применяются специализированные механизмы генерации кода.


Проблема прямой сериализации функций-валидаторов

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

const validate = ajv.compile(schema);

JSON.stringify(validate); // {}

Причины:

  • функция не является JSON-структурой;
  • внутренние свойства скрыты и не перечисляемы;
  • логика валидации хранится в исполняемом коде.

Даже validate.toString() не решает задачу полностью:

const fnSource = validate.toString();

Такой подход:

  • не гарантирует переносимость;
  • может ломаться при разных версиях движка;
  • не сохраняет зависимости Ajv runtime.

Генерация сериализуемого кода через standalone-режим

Основной промышленный способ сериализации в Ajv — генерация автономного модуля валидации.

В Ajv предусмотрен механизм standalone-компиляции, при котором схема превращается в самодостаточный JavaScript-файл.

Пример:

import Ajv from "ajv";
import standaloneCode from "ajv/dist/standalone";

const ajv = new Ajv({ code: { source: true } });

const schema = {
  type: "object",
  properties: {
    id: { type: "number" }
  },
  required: ["id"]
};

const validate = ajv.compile(schema);

const moduleCode = standaloneCode(ajv, validate);

На выходе получается строка JavaScript-кода, содержащая:

  • функцию валидации;
  • минимальный runtime Ajv;
  • встроенные проверки без зависимости от основной библиотеки.

Хранение сгенерированного кода

Сериализованный код может быть сохранён как обычный модуль:

import fs from "fs";

fs.writeFileSync("./validate.js", moduleCode);

После этого модуль используется без повторной компиляции:

import validate from "./validate.js";

validate({ id: 123 }); // true/false

Такой подход особенно полезен в сценариях:

  • serverless-окружения с холодным стартом;
  • edge runtime;
  • микросервисной архитектуры;
  • сборки фронтенда с предкомпиляцией схем.

Преимущества standalone-сериализации

Использование генерации кода даёт следующие эффекты:

1. Ускорение старта приложения

  • отсутствует runtime-компиляция схем;
  • нет необходимости загружать Ajv целиком.

2. Уменьшение зависимости

  • итоговый модуль может работать без полной библиотеки;
  • используется минимальный runtime.

3. Повышенная предсказуемость

  • код фиксируется на этапе сборки;
  • исключаются различия между средами исполнения.

Кэширование скомпилированных схем без сериализации

Альтернативный подход — хранение уже скомпилированных функций в памяти:

const cache = new Map();

function getValidator(ajv, schema) {
  const key = JSON.stringify(schema);

  if (!cache.has(key)) {
    cache.set(key, ajv.compile(schema));
  }

  return cache.get(key);
}

Этот метод:

  • не даёт настоящей сериализации;
  • работает только в рамках одного процесса;
  • подходит для серверных приложений с долгим жизненным циклом.

Ограничения сериализации в Ajv

Несмотря на наличие code generation, сериализация скомпилированных схем имеет ограничения:

1. Потеря динамических расширений

  • добавленные через addKeyword кастомные проверки должны быть заранее известны при генерации.

2. Зависимость от конфигурации Ajv

  • параметры strict, allErrors, formats должны совпадать при компиляции и исполнении.

3. Версионная совместимость

  • код, сгенерированный одной версией библиотеки, может некорректно работать в другой.

Интеграция сериализации в сборочный процесс

На практике сериализация часто переносится в этап build-time:

  • схема описывается в JSON/JS;
  • на этапе сборки выполняется компиляция Ajv;
  • генерируется набор валидаторов;
  • результат подключается как обычные модули.

Пример pipeline:

  1. Сканирование схем
  2. Компиляция через Ajv
  3. Генерация standalone-кода
  4. Запись файлов в dist/validators
  5. Импорт в приложение

Практическая модель использования

Типовой сценарий:

// build-time
const validateUser = ajv.compile(userSchema);
const code = standaloneCode(ajv, validateUser);
writeFile("validateUser.js", code);

// runtime
import validateUser from "./validateUser.js";

if (validateUser(data)) {
  // данные валидны
}

Такой подход превращает Ajv из runtime-инструмента в compile-time систему валидации, где проверка данных становится обычной функцией без накладных расходов на интерпретацию схем.