Автодополнение и типобезопасность

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

JSON Schema задаёт контракт: какие поля существуют, какие типы допустимы, какие значения являются валидными. Именно этот контракт используется одновременно в рантайме (через Ajv) и в среде разработки (через TypeScript и IDE).

Пример базовой схемы:

{
  "$id": "User",
  "type": "object",
  "properties": {
    "id": { "type": "number" },
    "email": { "type": "string", "format": "email" },
    "roles": {
      "type": "array",
      "items": { "type": "string" }
    }
  },
  "required": ["id", "email"],
  "additionalProperties": false
}

Такая схема уже является источником информации для:

  • валидации данных в Ajv
  • автодополнения в IDE
  • генерации TypeScript типов

Автодополнение в редакторе через JSON Schema

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

Пример привязки схемы:

{
  "$schema": "./user.schema.json",
  "id": 1,
  "email": "test@example.com",
  "roles": ["admin"]
}

При наличии $schema редактор получает информацию:

  • какие поля допустимы
  • какие типы ожидаются
  • какие значения разрешены
  • какие поля обязательны

Это даёт поведение, близкое к статически типизированным языкам, но без компиляции.


Ajv как слой строгой проверки типов

Ajv реализует рантайм-валидацию JSON Schema и при этом способен усиливать типобезопасность через строгие режимы.

Основные настройки, влияющие на типовую строгость:

import Ajv from "ajv";

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

Параметр strict: true заставляет библиотеку:

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

Это важно, потому что типобезопасность начинается не с данных, а с самой схемы.


Компиляция схемы как источник типовой информации

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

const validate = ajv.compile(schema);

const data = {
  id: 1,
  email: "user@mail.com",
  roles: ["admin"]
};

if (validate(data)) {
  // data считается валидным по схеме
} else {
  console.log(validate.errors);
}

На уровне архитектуры это создаёт эффект “runtime type guard”, который особенно важен для JavaScript.


Связка Ajv и TypeScript

TypeScript не выполняет валидацию данных во время выполнения, поэтому требуется мост между схемой и типами.

Подход 1: ручное описание типов

type User = {
  id: number;
  email: string;
  roles?: string[];
};

Проблема этого подхода — дублирование схемы и типов, что приводит к рассинхронизации.


Подход 2: вывод типов из JSON Schema

Используются утилиты вроде json-schema-to-ts.

import { FromSchema } from "json-schema-to-ts";

const userSchema = {
  type: "object",
  properties: {
    id: { type: "number" },
    email: { type: "string" }
  },
  required: ["id", "email"]
} as const;

type User = FromSchema<typeof userSchema>;

Теперь:

  • схема является единственным источником истины
  • TypeScript тип генерируется автоматически
  • Ajv и TS используют одну структуру

Типобезопасная компиляция в Ajv

Ajv можно связать с TypeScript так, чтобы валидатор одновременно выступал type guard.

import Ajv from "ajv";
import { FromSchema } from "json-schema-to-ts";

const ajv = new Ajv();

const schema = {
  type: "object",
  properties: {
    id: { type: "number" },
    email: { type: "string" }
  },
  required: ["id", "email"],
  additionalProperties: false
} as const;

type User = FromSchema<typeof schema>;

const validate = ajv.compile<User>(schema);

function parseUser(data: unknown): User | null {
  if (validate(data)) {
    return data;
  }
  return null;
}

Ключевой момент — параметризация compile<T>(), которая позволяет связать результат валидации с типом TypeScript.


Ограничения типовой безопасности в Ajv

Несмотря на интеграцию с TypeScript, существуют принципиальные ограничения:

  • TypeScript типы не влияют на runtime
  • JSON Schema может описывать динамические конструкции (if/then/else, anyOf)
  • не все схемы могут быть точно выражены в TS типах
  • generics и conditional schemas теряют точность при преобразовании

Особенно проблемны конструкции:

{
  "anyOf": [
    { "type": "string" },
    { "type": "number" }
  ]
}

В TypeScript это превращается в union, но сложные вложенные схемы могут давать неточные типы.


Автодополнение ключей схемы в TypeScript

Ajv не предоставляет полноценного автодополнения схемы, но TypeScript может усиливать разработку через as const.

const schema = {
  type: "object",
  properties: {
    id: { type: "number" },
    email: { type: "string" }
  }
} as const;

Благодаря as const:

  • поля становятся литеральными типами
  • IDE начинает предлагать корректные ключи JSON Schema
  • уменьшается риск опечаток

Строгая типизация через additionalProperties

Один из ключевых механизмов типобезопасности Ajv — контроль лишних полей.

{
  "type": "object",
  "properties": {
    "id": { "type": "number" }
  },
  "additionalProperties": false
}

Это напрямую влияет на типовую модель:

  • запрещает “лишние” поля
  • делает структуру данных фиксированной
  • позволяет TypeScript типам быть более точными

Генерация TypeScript типов из схемы как базовый подход

В крупных проектах часто используется генерация типов из JSON Schema как часть сборки.

Типичный поток:

  1. JSON Schema описывает контракт API
  2. Ajv использует схему для валидации
  3. TypeScript типы генерируются автоматически
  4. IDE использует типы для автодополнения

Это устраняет проблему расхождения:

  • схема = runtime
  • типы = compile-time

Standalone-компиляция и типовая стабильность

Ajv поддерживает генерацию standalone-валидаторов:

import standaloneCode from "ajv/dist/standalone";

const validate = ajv.compile(schema);

const code = standaloneCode(ajv, validate);

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

  • убирает overhead компиляции схемы
  • фиксирует структуру валидации
  • повышает предсказуемость поведения

В связке с TypeScript это усиливает стабильность контракта данных.


Типобезопасность через строгие форматы

Ajv поддерживает форматы (format), которые также влияют на типовую модель:

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

Хотя TypeScript не различает string и email, в runtime появляется дополнительный слой проверки, который делает данные более надёжными.

Дополнительные форматы подключаются через ajv-formats:

import addFormats from "ajv-formats";

addFormats(ajv);

Разрыв между runtime и compile-time моделями

Основная проблема типобезопасности в контексте Ajv заключается в разрыве двух миров:

  • JSON Schema — runtime контракт
  • TypeScript — compile-time контракт

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

  • строгая JSON Schema
  • Ajv в режиме strict
  • генерация TS типов
  • использование as const
  • type guards через compile<T>()

Поведение автодополнения в сложных схемах

При увеличении сложности схемы автодополнение становится менее точным:

  • вложенные allOf и oneOf усложняют вывод типов
  • динамические ключи (patternProperties) не всегда отображаются в IDE
  • рекурсивные схемы теряют читаемость в автодополнении

В таких случаях JSON Schema остаётся источником валидации, но не идеальным источником типов.


Итоговая модель взаимодействия

В типичной архитектуре с Ajv формируется трёхуровневая система:

  • JSON Schema — описание структуры данных
  • Ajv — runtime проверка и фильтрация
  • TypeScript / IDE — статическое автодополнение и проверка

Эти уровни не дублируют друг друга, а дополняют:

  • схема задаёт правила
  • Ajv гарантирует их выполнение
  • TypeScript обеспечивает безопасную разработку