Паттерны и регулярные выражения

Валидация строковых значений в JSON Schema часто опирается на регулярные выражения, задаваемые через ключевое слово pattern. В библиотеке Ajv эти выражения компилируются в нативные регулярные выражения JavaScript и применяются во время проверки данных.

Ключевой особенностью является то, что pattern работает исключительно с типом string и определяет ограничение на соответствие всей строки регулярному выражению.


Основное поведение pattern

Ключ pattern задаёт регулярное выражение в виде строки:

{
  "type": "string",
  "pattern": "^[a-zA-Z0-9_]+$"
}

Такое выражение ограничивает строку набором латинских букв, цифр и символа подчёркивания.

В Ajv регулярное выражение компилируется через конструктор RegExp, поэтому используется синтаксис JavaScript, а не PCRE или других диалектов.


Якоря и полное совпадение строки

Регулярное выражение в pattern не требует обязательного использования ^ и $, однако их отсутствие меняет семантику проверки.

  • ^ — начало строки
  • $ — конец строки

Пример строгой проверки идентификатора:

{
  "type": "string",
  "pattern": "^[a-z]+\\d{3}$"
}

Без якорей выражение может совпасть с подстрокой, что часто приводит к логическим ошибкам валидации.


Экранирование в JSON Schema

Регулярные выражения в JSON Schema записываются в виде JSON-строк, поэтому требуется двойное экранирование:

  • \d в регулярном выражении становится "\\d"
  • \. становится "\\."

Пример:

{
  "type": "string",
  "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
}

Это выражение соответствует дате в формате YYYY-MM-DD.


Поведение Ajv при компиляции pattern

Ajv при компиляции схемы превращает каждое регулярное выражение в объект RegExp. Это означает:

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

Включённый режим strict усиливает контроль за потенциально некорректными выражениями.


Unicode и многобайтовые символы

JavaScript-движок в Ajv поддерживает Unicode-строки, однако поведение зависит от флагов регулярного выражения.

При необходимости обработки Unicode рекомендуется использовать флаг u через inline-конструкции:

{
  "type": "string",
  "pattern": "^[\\p{L}]+$"
}

Такое выражение требует поддержки Unicode property escapes (\p{L}), доступных в современных окружениях.


Ограничения и особенности регулярных выражений

Регулярные выражения в Ajv имеют несколько важных ограничений:

  • не поддерживаются флаги, задаваемые отдельно (например i, m), если они не встроены в синтаксис
  • сложные backtracking-выражения могут существенно замедлять валидацию
  • отсутствие контроля за ReDoS-уязвимостями на уровне схемы

Поэтому выражения с множественными вложенными квантификаторами требуют осторожности:

{
  "pattern": "^(a+)+$"
}

Такие конструкции потенциально приводят к экспоненциальному времени выполнения.


patternProperties и динамические ключи

Для объектов регулярные выражения применяются через patternProperties, что позволяет валидировать ключи объекта:

{
  "type": "object",
  "patternProperties": {
    "^user_[0-9]+$": {
      "type": "string"
    }
  }
}

В этом случае все ключи, соответствующие шаблону user_число, должны иметь строковое значение.


Сочетание patternProperties и additionalProperties

Поведение при конфликте ключей регулируется комбинацией additionalProperties:

{
  "type": "object",
  "patternProperties": {
    "^id_": { "type": "number" }
  },
  "additionalProperties": false
}

Здесь разрешены только свойства, начинающиеся с id_. Любые другие ключи будут отклонены.


Производительность регулярных выражений

В Ajv производительность зависит от сложности выражений:

  • линейные выражения (^[a-z]+$) обрабатываются быстро
  • выражения с альтернативами и вложенными квантификаторами увеличивают время проверки
  • большое количество patternProperties приводит к последовательной проверке каждого ключа

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


Кэширование и повторное использование

Ajv компилирует схемы в функции валидации, где регулярные выражения уже инстанцированы. Это означает:

  • RegExp создаётся один раз при компиляции
  • повторные вызовы validate не создают новых объектов regex
  • повышение эффективности при массовой валидации данных

Типичные ошибки при использовании pattern

Распространённые проблемы:

  • отсутствие экранирования в JSON строке
  • забытые якоря ^ и $
  • использование неподдерживаемых конструкций regex
  • чрезмерная сложность выражения
  • логические ошибки в диапазонах символов

Пример некорректного паттерна:

{
  "pattern": "[a-zA-Z]+"
}

Такое выражение допускает частичное совпадение и не ограничивает строку полностью.


Взаимодействие с format и pattern

В Ajv одновременно могут использоваться format и pattern. Их семантика различается:

  • format — семантическая проверка (email, uri, date)
  • pattern — синтаксическая проверка через regex

Пример комбинирования:

{
  "type": "string",
  "format": "email",
  "pattern": "^[a-z0-9._%+-]+@example\\.com$"
}

Такое сочетание ограничивает email конкретным доменом.


Использование inline regex в сложных схемах

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

{
  "type": "string",
  "pattern": "^[A-Z]{2}-\\d{6}-[A-Z0-9]{4}$"
}

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


Поведение при ошибках валидации

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

  • keyword: pattern
  • dataPath: путь к значению
  • message: описание несоответствия

Ошибки можно агрегировать через allErrors, что позволяет получать полный список нарушений.


Роль pattern в архитектуре схем

Регулярные выражения в JSON Schema используются как инструмент строгой типизации строковых данных. В Ajv они выступают как низкоуровневый механизм контроля формата, дополняющий семантические проверки.

Часто pattern применяется в следующих сценариях:

  • валидация идентификаторов
  • проверка кодов систем
  • ограничение допустимых символов
  • контроль формата ключей объектов через patternProperties