JSON Schema представляет собой декларативное описание структуры данных, используемое для валидации, генерации документации и описания контрактов API. При работе с классами в JavaScript/TypeScript возникает задача преобразования объектной модели, основанной на классах и декораторах, в формализованную JSON Schema. В контексте экосистемы class-validator это достигается через анализ метаданных декораторов и последующую трансформацию в спецификацию JSON Schema.
Библиотека class-validator опирается на декораторы, которые описывают ограничения для свойств классов. Каждый декоратор формирует метаданные, фиксируемые через reflect-metadata, что позволяет извлекать правила валидации в рантайме.
Ключевые типы ограничений:
@IsString(),
@IsNumber(), @IsBoolean()@IsArray(),
@ValidateNested()@IsOptional(),
@IsDefined()@Min(),
@Max(), @Length(),
@Matches()@IsEnum()Каждое из этих ограничений потенциально может быть отображено в JSON
Schema как комбинация полей type, enum,
pattern, minimum, maximum,
items, required.
Генерация схемы невозможна без включённой поддержки метаданных TypeScript:
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
При включённой опции emitDecoratorMetadata компилятор
добавляет информацию о типах свойств, доступную через
Reflect.getMetadata. Это позволяет определить базовый тип
поля даже без явного декоратора class-validator.
Пример извлечения типа:
Reflect.getMetadata("design:type", User.prototype, "age");
Полученное значение используется как отправная точка для построения JSON Schema, которая затем уточняется декораторами.
Типичный класс с валидацией:
import { IsString, IsInt, Min, Max } from "class-validator";
class User {
@IsString()
name: string;
@IsInt()
@Min(0)
@Max(120)
age: number;
}
Соответствующая JSON Schema:
{
"type": "object",
"properties": {
"name": { "type": "string" },
"age": { "type": "integer", "minimum": 0, "maximum": 120 }
},
"required": ["name", "age"]
}
Логика трансформации:
@IsString() → "type": "string"@IsInt() → "type": "integer"@Min(n) → "minimum": n@Max(n) → "maximum": n@IsOptional() → добавление в
requiredНа практике генерация JSON Schema выполняется через библиотеку
class-validator-jsonschema, которая агрегирует метаданные
class-validator и class-transformer.
Установка:
npm install class-validator class-transformer class-validator-jsonschema reflect-metadata
Генерация схемы:
import "reflect-metadata";
import { validationMetadatasToSchemas } from "class-validator-jsonschema";
import { getMetadataStorage } from "class-validator";
const schemas = validationMetadatasToSchemas({
classValidatorMetadataStorage: getMetadataStorage()
});
Результат содержит JSON Schema для всех зарегистрированных классов.
При использовании @ValidateNested() возникает
необходимость рекурсивной обработки вложенных объектов.
import { ValidateNested, IsString } from "class-validator";
import { Type } from "class-transformer";
class Address {
@IsString()
city: string;
}
class User {
@ValidateNested()
@Type(() => Address)
address: Address;
}
JSON Schema:
{
"type": "object",
"properties": {
"address": {
"$ref": "#/definitions/Address"
}
}
}
Дополнительно формируется секция definitions, содержащая
описание Address.
Механизм работы:
@ValidateNested() сигнализирует о вложенной
структуре@Type(() => Class) позволяет определить конкретный
класс для построения схемы$ref вместо дублирования
структурыМассивы требуют комбинации @IsArray() и вложенной
валидации элементов.
class User {
@IsString({ each: true })
tags: string[];
}
JSON Schema:
{
"type": "object",
"properties": {
"tags": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
Ключевые преобразования:
@IsArray() → "type": "array"each: true → применение ограничения к
items@IsEnum() преобразуется в JSON Schema через
enum.
enum Role {
Admin = "admin",
User = "user"
}
class User {
@IsEnum(Role)
role: Role;
}
JSON Schema:
{
"type": "object",
"properties": {
"role": {
"enum": ["admin", "user"]
}
}
}
Если enum числовой, сохраняется соответствующая числовая схема без преобразования типов.
@IsOptional() влияет на секцию required.
Поле исключается из списка обязательных свойств.
class User {
@IsOptional()
@IsString()
nickname?: string;
}
JSON Schema:
{
"type": "object",
"properties": {
"nickname": { "type": "string" }
}
}
Отсутствие required означает допустимость
undefined.
Строковые ограничения:
@Length(min, max) → minLength,
maxLength@Matches(regex) → patternclass User {
@Matches(/^[a-z]+$/)
username: string;
}
JSON Schema:
{
"username": {
"type": "string",
"pattern": "^[a-z]+$"
}
}
Числовые ограничения:
@Min() → minimum@Max() → maximum@IsInt() → integerПри наличии нескольких декораторов формируется пересечение ограничений. Порядок применения декораторов не влияет на результат, так как метаданные агрегируются.
Пример:
class Product {
@IsString()
@Length(3, 20)
title: string;
}
Результат:
{
"title": {
"type": "string",
"minLength": 3,
"maxLength": 20
}
}
class-validator позволяет создавать кастомные декораторы через
registerDecorator. Такие ограничения не имеют прямого
соответствия в JSON Schema и требуют ручного маппинга.
import { registerDecorator } from "class-validator";
function IsEven() {
return function (object: Object, propertyName: string) {
registerDecorator({
name: "isEven",
target: object.constructor,
propertyName,
validator: {
validate(value: number) {
return value % 2 === 0;
}
}
});
};
}
JSON Schema не содержит стандартного аналога, поэтому возможные стратегии:
x- поляpattern или modulo (не
стандарт JSON Schema)Пример расширения:
{
"x-isEven": true
}
Несмотря на схожесть целей, модели различаются:
Ключевые расхождения:
undefined и nullДополнительно JSON Schema требует явного указания
nullable (в зависимости от версии спецификации), тогда как
class-validator работает с undefined через
@IsOptional().
Библиотеки генерации обычно предоставляют настройки:
requireddefinitions или inline-схемx-Пример конфигурации:
validationMetadatasToSchemas({
classValidatorMetadataStorage: getMetadataStorage(),
refPointerPrefix: "#/definitions/"
});
Результирующая JSON Schema может быть типизирована:
type JSONSchema = {
type?: string;
properties?: Record<string, JSONSchema>;
required?: string[];
};
Это позволяет использовать схему для:
При масштабных проектах схема используется как единый источник правды для:
Связка class-validator + JSON Schema обеспечивает переход от императивной валидации к декларативному контракту данных, где классы выступают первичной моделью, а схема — производным артефактом.