В экосистеме 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
}
Такая схема уже является источником информации для:
Редакторы, такие как VS Code, умеют использовать JSON Schema напрямую. При привязке схемы к файлу конфигурации или данным появляется статическое автодополнение.
Пример привязки схемы:
{
"$schema": "./user.schema.json",
"id": 1,
"email": "test@example.com",
"roles": ["admin"]
}
При наличии $schema редактор получает информацию:
Это даёт поведение, близкое к статически типизированным языкам, но без компиляции.
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.
TypeScript не выполняет валидацию данных во время выполнения, поэтому требуется мост между схемой и типами.
type User = {
id: number;
email: string;
roles?: string[];
};
Проблема этого подхода — дублирование схемы и типов, что приводит к рассинхронизации.
Используются утилиты вроде 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>;
Теперь:
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.
Несмотря на интеграцию с TypeScript, существуют принципиальные ограничения:
if/then/else, anyOf)Особенно проблемны конструкции:
{
"anyOf": [
{ "type": "string" },
{ "type": "number" }
]
}
В TypeScript это превращается в union, но сложные вложенные схемы могут давать неточные типы.
Ajv не предоставляет полноценного автодополнения схемы, но TypeScript
может усиливать разработку через as const.
const schema = {
type: "object",
properties: {
id: { type: "number" },
email: { type: "string" }
}
} as const;
Благодаря as const:
additionalPropertiesОдин из ключевых механизмов типобезопасности Ajv — контроль лишних полей.
{
"type": "object",
"properties": {
"id": { "type": "number" }
},
"additionalProperties": false
}
Это напрямую влияет на типовую модель:
В крупных проектах часто используется генерация типов из JSON Schema как часть сборки.
Типичный поток:
Это устраняет проблему расхождения:
Ajv поддерживает генерацию standalone-валидаторов:
import standaloneCode from "ajv/dist/standalone";
const validate = ajv.compile(schema);
const code = standaloneCode(ajv, validate);
Такой подход:
В связке с TypeScript это усиливает стабильность контракта данных.
Ajv поддерживает форматы (format), которые также влияют
на типовую модель:
{
"type": "string",
"format": "email"
}
Хотя TypeScript не различает string и
email, в runtime появляется дополнительный слой проверки,
который делает данные более надёжными.
Дополнительные форматы подключаются через
ajv-formats:
import addFormats from "ajv-formats";
addFormats(ajv);
Основная проблема типобезопасности в контексте Ajv заключается в разрыве двух миров:
Ajv выступает связующим звеном, но не устраняет фундаментальную разницу. Поэтому типобезопасность достигается не одной технологией, а их связкой:
as constcompile<T>()При увеличении сложности схемы автодополнение становится менее точным:
allOf и oneOf усложняют вывод
типовpatternProperties) не всегда
отображаются в IDEВ таких случаях JSON Schema остаётся источником валидации, но не идеальным источником типов.
В типичной архитектуре с Ajv формируется трёхуровневая система:
Эти уровни не дублируют друг друга, а дополняют: