Одним из ключевых преимуществ использования JSON Schema в TypeScript-проектах является возможность автоматической генерации типов на основе схем валидации. Это устраняет дублирование описаний данных, снижает вероятность рассинхронизации между рантайм-валидацией и типами на этапе компиляции и делает контракт данных единым источником истины.
Ajv сам по себе является валидатором JSON Schema, но в экосистеме вокруг него существует несколько подходов к генерации TypeScript-типов из схем, а также интеграции с типами на этапе сборки и выполнения.
JSON Schema описывает структуру данных на уровне выполнения, тогда как TypeScript-типизация работает на этапе компиляции. Основная проблема возникает, когда схема и тип описываются вручную:
type User = {
id: number;
name: string;
};
и отдельно:
const schema = {
type: "object",
properties: {
id: { type: "number" },
name: { type: "string" }
},
required: ["id", "name"]
};
Любое расхождение между ними приводит к логическим ошибкам, которые TypeScript не всегда способен обнаружить.
Решение заключается в генерации типов из схем или использовании схемы как первичного источника.
Схема остаётся источником истины, а типы генерируются автоматически.
Наиболее распространённый инструмент —
json-schema-to-typescript:
npm install -D json-schema-to-typescript
{
"$id": "User",
"type": "object",
"properties": {
"id": { "type": "integer" },
"email": { "type": "string", "format": "email" },
"isActive": { "type": "boolean" }
},
"required": ["id", "email"]
}
json2ts -i user.schema.json -o user.types.ts
Результат:
export interface User {
id: number;
email: string;
isActive?: boolean;
}
Сгенерированные типы не заменяют Ajv, а работают вместе с ним:
import Ajv from "ajv";
import schema from "./user.schema.json";
const ajv = new Ajv();
const validate = ajv.compile(schema);
const data = {
id: 1,
email: "test@mail.com"
};
if (validate(data)) {
// data имеет тип User (в TypeScript)
}
Типизация и валидация становятся согласованными через общую схему.
В более современных подходах схема не пишется вручную, а выводится из TypeScript.
Хотя это не чистый Ajv-подход, он часто применяется вместе.
Пример с TypeBox:
import { Type } from "@sinclair/typebox";
export const User = Type.Object({
id: Type.Number(),
email: Type.String({ format: "email" })
});
Далее схема может быть использована напрямую в Ajv:
import Ajv from "ajv";
import { User } from "./schema";
const ajv = new Ajv();
const validate = ajv.compile(User);
TypeScript тип выводится автоматически:
type UserType = Static<typeof User>;
Ajv не является генератором типов напрямую, но предоставляет runtime-структуру, которую можно использовать совместно с дополнительными инструментами.
В некоторых проектах применяется генерация типов через промежуточное AST-представление схем.
Пример:
import Ajv from "ajv";
import addFormats from "ajv-formats";
const ajv = new Ajv();
addFormats(ajv);
const schema = {
type: "object",
properties: {
age: { type: "number", minimum: 0 }
},
required: ["age"]
};
const validate = ajv.compile(schema);
Далее тип может быть выведен вручную или через генератор схем.
Ajv CLI позволяет интегрировать схемы в сборочный процесс.
npm install -D ajv-cli
Проверка данных:
ajv validate -s schema.json -d data.json
Хотя CLI не генерирует TypeScript напрямую, он часто используется в пайплайне, где параллельно работает генератор типов.
{
"oneOf": [
{ "type": "string" },
{ "type": "number" }
]
}
Генерация типов:
type Value = string | number;
{
"type": "object",
"properties": {
"profile": {
"type": "object",
"properties": {
"nickname": { "type": "string" }
},
"required": ["nickname"]
}
}
}
TypeScript:
type User = {
profile: {
nickname: string;
};
};
{
"type": "array",
"items": {
"type": "number"
}
}
type Numbers = number[];
Основная проблема при генерации типов — рассинхронизация build-процессов.
Типовой подход:
/schemasПример скрипта:
{
"scripts": {
"generate:types": "json2ts -i schemas -o types",
"build": "npm run generate:types && tsc"
}
}
Ajv может усиливать соответствие схем:
const ajv = new Ajv({
allErrors: true,
strict: true,
strictTypes: true
});
Это помогает избежать ситуаций, когда схема допускает типы, не отражённые в TypeScript-генерации.
{
"type": ["string", "null"]
}
TypeScript:
string | null
Но при сложных композициях возможны неточные выводы.
{
"type": "object",
"additionalProperties": {
"type": "string"
}
}
TypeScript:
Record<string, string>
При сложных ограничениях теряется детализация.
Ajv поддерживает кастомные ключевые слова:
ajv.addKeyword("x-custom-rule");
Но генераторы типов их часто игнорируют, что приводит к расхождению логики.
Наиболее устойчивый подход:
Структура:
schemas/
user.schema.json
types/
user.types.ts
validators/
user.validator.ts
Некоторые сборки используют генерацию валидаторов:
const validateUser = ajv.compile<User>(schema);
Хотя Ajv не всегда выводит типы автоматически, через обёртки можно добиться строгой типизации.
Генерация типов из JSON Schema в экосистеме Ajv — это не встроенная функция библиотеки, а архитектурный паттерн, который строится вокруг неё. Основная ценность Ajv в этом контексте заключается в том, что он обеспечивает строгую и быструю runtime-валидацию, а типы формируются либо внешними генераторами, либо через schema-first подход.
На практике наиболее устойчивые системы используют схему как единственный источник данных, из которого одновременно извлекаются валидаторы и типы, минимизируя дублирование и вероятность логических расхождений между слоями приложения.