Добавление собственных форматов

Валидация строковых значений в схемах JSON часто требует не только базовых проверок типа или длины, но и проверки структуры: email, телефон, UUID, даты, идентификаторы внутренних систем. В библиотеке Ajv для этого используется механизм форматов (formats).

Формат в Ajv — это именованное правило проверки строкового значения, которое применяется через ключ format в JSON Schema.

Пример использования встроенного формата:

{
  "type": "string",
  "format": "email"
}

В этом случае Ajv применяет стандартную проверку email. Однако встроенных форматов недостаточно для прикладных систем, поэтому предусмотрена возможность добавления собственных.


Механизм регистрации пользовательских форматов

Добавление форматов осуществляется через метод addFormat.

const Ajv = require("ajv");
const ajv = new Ajv();

ajv.addFormat("uppercase", {
  type: "string",
  validate: (data) => data === data.toUpperCase()
});

После регистрации формат может использоваться в схемах:

const schema = {
  type: "string",
  format: "uppercase"
};

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


Виды пользовательских форматов

Строковый формат через регулярное выражение

Наиболее простой вариант — использование регулярного выражения:

ajv.addFormat("productCode", /^[A-Z]{3}-\d{4}$/);

Такой формат проверяет строки вида:

ABC-1234

Регулярное выражение автоматически интерпретируется как функция проверки.

Особенности:

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

Функциональный формат

Функция предоставляет полный контроль над логикой проверки:

ajv.addFormat("even-number", {
  type: "number",
  validate: (value) => Number.isInteger(value) && value % 2 === 0
});

Формат может работать не только со строками, но и с числами, если явно указан type.

Пример схемы:

{
  type: "number",
  format: "even-number"
}

Функциональные форматы используются при необходимости:

  • сложных условий проверки;
  • обращения к внешним данным (в ограниченном виде);
  • нестандартной логики.

Форматы с обработкой ошибок

Функция может возвращать не только true/false, но и строку с описанием ошибки:

ajv.addFormat("starts-with-A", {
  type: "string",
  validate: (value) => {
    if (value.startsWith("A")) return true;
    return "строка должна начинаться с буквы A";
  }
});

В этом случае сообщение становится частью ошибки валидации.


Типизация форматов

Каждый формат может быть привязан к конкретному типу данных:

  • string
  • number
  • boolean
  • integer

Пример:

ajv.addFormat("positive-int", {
  type: "integer",
  validate: (value) => value > 0
});

Если тип значения не совпадает, формат не применяется.


Поведение при строгом режиме

При включённом строгом режиме Ajv может выдавать предупреждения или ошибки при использовании неизвестных форматов:

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

Если схема содержит:

{ "format": "custom-format" }

и формат не зарегистрирован, возникает ошибка компиляции схемы.


Переопределение встроенных форматов

Существующие форматы можно переопределять:

ajv.addFormat("email", {
  type: "string",
  validate: (value) => value.endsWith("@example.com")
});

В этом случае стандартная проверка email заменяется пользовательской логикой.

Такой подход используется для:

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

Удаление форматов

В некоторых конфигурациях поддерживается удаление форматов:

ajv.removeFormat("email");

После удаления использование формата в схемах приводит к ошибке или игнорированию, в зависимости от настроек строгого режима.


Переиспользование форматов между экземплярами

Форматы регистрируются на уровне экземпляра Ajv:

const ajv1 = new Ajv();
const ajv2 = new Ajv();

Формат, добавленный в ajv1, не доступен в ajv2.

Для переиспользования применяется отдельный модуль или функция инициализации:

function registerFormats(ajv) {
  ajv.addFormat("uuid", /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i);
}

Практические примеры пользовательских форматов

UUID

ajv.addFormat("uuid", /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i);

Использование:

{
  type: "string",
  format: "uuid"
}

Телефонный номер

ajv.addFormat("phone", /^\+7\d{10}$/);

Формат фиксирует российский международный номер.


Дата в ISO 8601

ajv.addFormat("iso-date", (value) => {
  const d = new Date(value);
  return !isNaN(d.getTime()) && value.includes("T");
});

Строгий код продукта

ajv.addFormat("sku", {
  type: "string",
  validate: (v) => /^[A-Z]{2}-\d{6}$/.test(v)
});

Влияние форматов на производительность

Форматы выполняются на этапе валидации каждого значения, поэтому их эффективность критична:

  • регулярные выражения работают быстрее функций;
  • сложные вычисления внутри validate увеличивают время проверки;
  • повторно используемые форматы кэшируются при компиляции схемы.

При массовой валидации (например, обработка массивов объектов) оптимизация форматов напрямую влияет на пропускную способность.


Ограничения форматов

Форматы в Ajv имеют ряд ограничений:

  • применяются только к примитивным значениям (в основном строкам и числам);
  • не могут изменять данные, только проверяют;
  • не получают контекст схемы или родительские узлы;
  • не предназначены для асинхронной логики.

Для более сложной логики используются custom keywords, а не formats.


Сочетание форматов с другими правилами схемы

Форматы работают совместно с другими ключами JSON Schema:

{
  type: "string",
  format: "uuid",
  minLength: 36,
  maxLength: 36
}

Проверка выполняется последовательно:

  1. проверка типа;
  2. проверка длины;
  3. проверка формата.

Использование форматов в больших схемах

В крупных схемах форматы часто выносятся в единый слой и стандартизируются:

ajv.addFormat("order-id", /^[0-9]{10}$/);
ajv.addFormat("user-id", /^[A-Z0-9]{8}$/);
ajv.addFormat("timestamp", /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/);

Это позволяет:

  • унифицировать правила валидации;
  • снизить дублирование;
  • централизовать изменение логики.

Поведение при ошибках в формате

Если значение не проходит проверку формата, Ajv формирует стандартную ошибку:

{
  "keyword": "format",
  "message": "must match format \"uuid\""
}

При использовании кастомных сообщений из validate они добавляются вместо стандартного текста.


Архитектурная роль форматов

Форматы выполняют функцию декларативных ограничителей строки, отделяя:

  • структуру данных (schema);
  • правила формата (format);
  • бизнес-логику (keywords или внешняя обработка).

Это разделение упрощает поддержку и масштабирование схем в сложных системах валидации.