Плагины и расширения

Ajv поддерживает систему расширений, позволяющую адаптировать валидатор под требования проекта: добавлять собственные ключевые слова, форматы, трансформации данных, интеграцию с TypeScript и генерацию ошибок.

Механизм расширения строится вокруг нескольких направлений:

  • пользовательские ключевые слова (addKeyword)
  • пользовательские форматы (addFormat)
  • плагины (ajv-formats, ajv-errors, ajv-keywords)
  • асинхронная валидация
  • генерация кода валидатора
  • интеграция с TypeScript
  • расширение meta-schema

Базовый экземпляр Ajv:

const Ajv = require("ajv")

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

Подключение плагинов

Большинство расширений устанавливается отдельно.

Установка

npm install ajv
npm install ajv-formats
npm install ajv-keywords
npm install ajv-errors

Подключение

const Ajv = require("ajv")
const addFormats = require("ajv-formats")
const addKeywords = require("ajv-keywords")

const ajv = new Ajv()

addFormats(ajv)
addKeywords(ajv)

Плагин получает экземпляр Ajv и модифицирует его внутренний реестр.


ajv-formats

Назначение

В стандарт JSON Schema входят форматы:

  • email
  • uri
  • date
  • ipv4
  • uuid

Однако Ajv v7+ вынес их в отдельный пакет.

Без подключения ajv-formats проверка format не выполняется.


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

email

const schema = {
  type: "string",
  format: "email"
}

const validate = ajv.compile(schema)

console.log(validate("admin@example.com"))
console.log(validate("wrong-email"))

Проверка URL

const schema = {
  type: "string",
  format: "uri"
}

Проверка UUID

const schema = {
  type: "string",
  format: "uuid"
}

Проверка даты

const schema = {
  type: "string",
  format: "date"
}

Формат соответствует ISO 8601:

2025-05-10

Режимы работы форматов

Полная проверка

const ajv = new Ajv({
  validateFormats: true
})

Игнорирование форматов

const ajv = new Ajv({
  validateFormats: false
})

Форматы с сравнением

ajv-formats добавляет поддержку:

  • formatMinimum
  • formatMaximum
  • formatExclusiveMinimum
  • formatExclusiveMaximum

Пример:

const schema = {
  type: "string",
  format: "date",
  formatMinimum: "2024-01-01"
}

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

addFormat

Ajv позволяет создавать собственные форматы.

Пример: HEX-цвет

ajv.addFormat("hex-color", {
  type: "string",
  validate: (value) => {
    return /^#([0-9A-F]{3}|[0-9A-F]{6})$/i.test(value)
  }
})

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

const schema = {
  type: "string",
  format: "hex-color"
}

Форматы для чисел

Формат может работать с number.

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

Форматы на основе RegExp

Упрощённый вариант:

ajv.addFormat("slug", /^[a-z0-9-]+$/)

ajv-keywords

Назначение

ajv-keywords добавляет множество дополнительных ключевых слов JSON Schema.

Среди них:

  • transform
  • uniqueItemProperties
  • instanceof
  • regexp
  • deepRequired
  • dynamicDefaults
  • select
  • range

transform

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

trim

const schema = {
  type: "string",
  transform: ["trim"]
}

trim + toLowerCase

const schema = {
  type: "string",
  transform: ["trim", "toLowerCase"]
}

uniqueItemProperties

Проверка уникальности свойства в массиве объектов.

const schema = {
  type: "array",
  uniqueItemProperties: ["id"]
}

Данные:

[
  { id: 1 },
  { id: 2 }
]

instanceof

Проверка экземпляра класса.

const schema = {
  instanceof: "Date"
}

regexp

Расширенная работа с регулярными выражениями.

const schema = {
  type: "string",
  regexp: "/^[A-Z]+$/"
}

deepRequired

Проверка вложенных полей.

const schema = {
  deepRequired: ["/user/profile/name"]
}

range

Альтернатива minimum и maximum.

const schema = {
  type: "number",
  range: [1, 10]
}

ajv-errors

Назначение

ajv-errors позволяет задавать собственные сообщения об ошибках.


Подключение

const Ajv = require("ajv")
const ajvErrors = require("ajv-errors")

const ajv = new Ajv({
  allErrors: true
})

ajvErrors(ajv)

Пользовательские сообщения

const schema = {
  type: "object",
  required: ["email"],
  properties: {
    email: {
      type: "string",
      format: "email"
    }
  },
  errorMessage: {
    required: {
      email: "Поле email обязательно"
    },
    properties: {
      email: "Некорректный email"
    }
  }
}

Общие сообщения

errorMessage: "Данные не прошли валидацию"

Сообщения для свойств

errorMessage: {
  properties: {
    age: "Возраст указан неверно"
  }
}

Пользовательские ключевые слова

addKeyword

Главный механизм расширения Ajv.

ajv.addKeyword({
  keyword: "even",
  type: "number",
  validate(schema, data) {
    return data % 2 === 0
  }
})

Схема:

const schema = {
  type: "number",
  even: true
}

Структура пользовательского keyword

Основные поля:

Поле Назначение
keyword имя keyword
type тип данных
schemaType тип значения keyword
validate функция проверки
compile генератор валидатора
code генерация кода
metaSchema схема keyword
errors поддержка ошибок

validate

Простая реализация

ajv.addKeyword({
  keyword: "isPrime",
  type: "number",
  validate(schema, data) {
    if (!schema) return true

    for (let i = 2; i < data; i++) {
      if (data % i === 0) return false
    }

    return data > 1
  }
})

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

const schema = {
  type: "number",
  isPrime: true
}

compile

Предкомпиляция validator

compile вызывается один раз при создании валидатора.

ajv.addKeyword({
  keyword: "startsWith",

  compile(prefix) {
    return function (data) {
      return data.startsWith(prefix)
    }
  }
})

Преимущества compile

  • выше производительность
  • отсутствие повторных вычислений
  • возможность кэширования
  • меньше нагрузка GC

code generation

Генерация JavaScript-кода

Ajv генерирует оптимизированный JS-код валидатора.

Пользовательские keywords могут участвовать в генерации.

ajv.addKeyword({
  keyword: "positive",

  code(cxt) {
    const { data } = cxt

    cxt.fail(`${data} <= 0`)
  }
})

Почему code быстрее

Подход validate вызывает функции во время выполнения.

Подход code генерирует итоговый JS-валидатор без дополнительных вызовов.


macro keywords

Макросы

Keyword может преобразовываться в другую JSON Schema.

ajv.addKeyword({
  keyword: "nonEmptyString",

  macro() {
    return {
      type: "string",
      minLength: 1
    }
  }
})

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

const schema = {
  nonEmptyString: true
}

async keywords

Асинхронная валидация

Ajv поддерживает Promise-based validation.


Асинхронный keyword

ajv.addKeyword({
  keyword: "userExists",
  async: true,

  validate: async function (schema, data) {
    const user = await db.findUser(data)

    return !!user
  }
})

Async schema

const schema = {
  $async: true,
  type: "string",
  userExists: true
}

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

try {
  await validate("admin")
} catch (err) {
  console.log(err.errors)
}

errors в custom keyword

Пользовательские ошибки

ajv.addKeyword({
  keyword: "even",
  errors: true,

  validate(schema, data) {
    const valid = data % 2 === 0

    if (!valid) {
      this.errors = [
        {
          keyword: "even",
          message: "число должно быть чётным"
        }
      ]
    }

    return valid
  }
})

metaSchema

Валидация keyword

metaSchema определяет структуру самого keyword.

ajv.addKeyword({
  keyword: "range",

  metaSchema: {
    type: "array",
    items: { type: "number" },
    minItems: 2,
    maxItems: 2
  }
})

dependencies между keyword

implements

Keyword может объявлять зависимость.

ajv.addKeyword({
  keyword: "range",
  implements: ["exclusiveRange"]
})

modifying keywords

Изменение данных

Keyword может мутировать входные данные.

ajv.addKeyword({
  keyword: "appendX",
  modifying: true,

  validate(schema, data, parentSchema, ctx) {
    ctx.parentData[ctx.parentDataProperty] += "X"

    return true
  }
})

inline keywords

Встраивание выражений

ajv.addKeyword({
  keyword: "positive",

  inline() {
    return "(data > 0)"
  }
})

Подход использовался в старых версиях Ajv и постепенно заменяется code.


Расширение meta-schema

addMetaSchema

Ajv позволяет добавлять собственные стандарты схем.

ajv.addMetaSchema({
  $id: "custom-schema",

  type: "object",
  properties: {
    type: {
      type: "string"
    }
  }
})

Пользовательские vocabularies

addVocabulary

Можно регистрировать наборы keyword одновременно.

ajv.addVocabulary([
  "range",
  "positive",
  "nonEmptyString"
])

ajv-i18n

Локализация ошибок

ajv-i18n переводит ошибки Ajv.


Установка

npm install ajv-i18n

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

const localize = require("ajv-i18n")

localize.ru(validate.errors)

После локализации:

console.log(validate.errors)

ajv-cli

CLI-инструмент

ajv-cli предоставляет командную строку для проверки JSON.


Установка

npm install -g ajv-cli

Валидация файла

ajv validate -s schema.json -d data.json

Проверка нескольких файлов

ajv validate -s schema.json -d "data/*.json"

ajv-pack

Генерация standalone validator

Ajv умеет генерировать автономные валидаторы без runtime Ajv.

const standaloneCode = require("ajv/dist/standalone")

Генерация

const moduleCode = standaloneCode(ajv, validate)

Преимущества standalone

  • минимальный runtime
  • быстрый startup
  • использование в browser
  • отсутствие зависимости от Ajv на production

TypeScript и расширения

Типизация custom keyword

import Ajv, { KeywordDefinition } from "ajv"

const keyword: KeywordDefinition = {
  keyword: "positive",

  type: "number",

  validate(schema: boolean, data: number) {
    return data > 0
  }
}

JSONSchemaType

Типобезопасные схемы:

interface User {
  name: string
  age: number
}
const schema: JSONSchemaType<User> = {
  type: "object",

  properties: {
    name: { type: "string" },
    age: { type: "number" }
  },

  required: ["name", "age"],
  additionalProperties: false
}

Создание собственного плагина

Базовая структура

module.exports = function (ajv) {
  ajv.addKeyword({
    keyword: "positive",

    validate(schema, data) {
      return data > 0
    }
  })

  return ajv
}

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

const positivePlugin = require("./positive-plugin")

positivePlugin(ajv)

Композиция плагинов

Объединение расширений

function setupAjv(ajv) {
  addFormats(ajv)
  addKeywords(ajv)

  ajvErrors(ajv)

  return ajv
}

Производительность расширений

validate vs code

Подход Скорость
validate ниже
compile выше
code максимальная

Наиболее дорогие операции

Особенно затратны:

  • асинхронные validators
  • регулярные выражения
  • глубокая рекурсия
  • mutating keywords
  • dynamic schema generation

Практика организации расширений

Выделение отдельного модуля

// validation/index.js
module.exports = function createAjv() {
  const ajv = new Ajv({
    allErrors: true
  })

  addFormats(ajv)
  addKeywords(ajv)

  return ajv
}

Группировка keyword

// validation/keywords/index.js
module.exports = function (ajv) {
  require("./positive")(ajv)
  require("./range")(ajv)
  require("./slug")(ajv)
}

Рекомендации по разработке custom keyword

Предпочтение declarative schema

Лучше:

{
  type: "string",
  minLength: 3
}

Чем:

{
  customValidation: true
}

Минимизация modifying keyword

Изменение входных данных усложняет отладку и делает поведение неявным.


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

metaSchema предотвращает ошибки конфигурации на этапе компиляции схемы.


Избегание тяжёлой логики в validate

Сложные вычисления лучше переносить в:

  • compile
  • code
  • preprocessing

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

Для production-систем с высокой нагрузкой standalone validators существенно уменьшают накладные расходы.