Механизм валидации в библиотеке строится вокруг объекта
ValidationError, который возвращается после выполнения
функции validate() или validateSync(). Каждый
такой объект описывает не только факт ошибки, но и её точное место в
структуре данных, включая вложенные поля и массивы.
Базовая структура ошибки:
interface ValidationError {
target?: object;
property: string;
value?: any;
constraints?: {
[type: string]: string;
};
children?: ValidationError[];
contexts?: {
[type: string]: any;
};
}
Ключевое значение здесь имеет поле constraints. Оно
содержит набор нарушенных правил, где ключ — тип валидатора, а значение
— текст ошибки.
Пример результата:
{
property: "email",
value: "wrong-email",
constraints: {
isEmail: "email must be an email"
}
}
Функция validate() возвращает массив ошибок. Даже если
ошибка одна, результат всегда массив.
import { validate } from "class-validator";
const errors = await validate(user);
Каждый элемент массива соответствует одному полю, нарушившему правила.
Для извлечения ошибок конкретного свойства используется обход массива:
const emailError = errors.find(err => err.property === "email");
Дальнейший доступ к причинам ошибки:
const message = emailError?.constraints
? Object.values(emailError.constraints)[0]
: null;
Часто требуется не структура, а плоский список текстов ошибок. Для этого используется рекурсивный обход:
function extractMessages(errors) {
const messages = [];
for (const error of errors) {
if (error.constraints) {
messages.push(...Object.values(error.constraints));
}
if (error.children && error.children.length) {
messages.push(...extractMessages(error.children));
}
}
return messages;
}
Результат — единый массив строк без структуры:
[
"email must be an email",
"password must be longer than 8 characters"
]
При использовании вложенных DTO (@ValidateNested) ошибки
приобретают древовидную структуру.
Пример:
class Profile {
@IsString()
city: string;
}
class User {
@ValidateNested()
profile: Profile;
}
Ошибка внутри profile будет находиться в
children:
const profileError = errors.find(e => e.property === "profile");
const cityError = profileError?.children?.find(
e => e.property === "city"
);
Для сложных структур удобнее использовать функцию поиска по пути:
function findErrorByPath(errors, path) {
const parts = path.split(".");
let current = errors;
for (const part of parts) {
const found = current.find(e => e.property === part);
if (!found) return null;
current = found.children || [];
}
return current;
}
Использование:
const error = findErrorByPath(errors, "profile.city");
Поле constraints может содержать несколько нарушений
одновременно:
{
constraints: {
isString: "must be a string",
minLength: "too short"
}
}
Извлечение всех типов ошибок:
const types = Object.keys(error.constraints);
Извлечение первого сообщения:
const firstMessage = Object.values(error.constraints)[0];
Часто требуется преобразовать ошибки в формат API:
function normalizeErrors(errors) {
return errors.map(err => ({
field: err.property,
messages: err.constraints
? Object.values(err.constraints)
: [],
children: err.children?.length
? normalizeErrors(err.children)
: []
}));
}
Результат становится удобным для фронтенда:
[
{
field: "email",
messages: ["email must be an email"],
children: []
}
]
Иногда важно найти ошибки конкретного типа, например
isNotEmpty:
function findByConstraint(errors, type) {
const result = [];
for (const error of errors) {
if (error.constraints?.[type]) {
result.push(error.constraints[type]);
}
if (error.children?.length) {
result.push(...findByConstraint(error.children, type));
}
}
return result;
}
Пример:
findByConstraint(errors, "isNotEmpty");
Для упрощённой диагностики можно получить список проблемных полей:
function collectFields(errors) {
const fields = [];
for (const err of errors) {
fields.push(err.property);
if (err.children?.length) {
fields.push(...collectFields(err.children));
}
}
return fields;
}
При глубоких структурах ошибки могут дублироваться. Используется Set:
function uniqueMessages(errors) {
const set = new Set();
const walk = (errs) => {
for (const e of errs) {
if (e.constraints) {
Object.values(e.constraints).forEach(m => set.add(m));
}
if (e.children?.length) {
walk(e.children);
}
}
};
walk(errors);
return [...set];
}
Синхронная версия позволяет работать без async:
import { validateSync } from "class-validator";
const errors = validateSync(user);
Дальнейшая обработка идентична, так как структура ошибок не отличается.
Часто требуется простое отображение:
function mapFieldErrors(errors) {
const result = {};
for (const err of errors) {
result[err.property] = err.constraints
? Object.values(err.constraints)[0]
: null;
}
return result;
}
Пример результата:
{
email: "email must be an email",
password: "password must be longer than 8 characters"
}
Некоторые валидаторы добавляют дополнительную информацию:
{
contexts: {
minLength: {
required: 8,
actual: 5
}
}
}
Извлечение контекста:
const context = error.contexts?.minLength;
Это позволяет строить более информативные сообщения на уровне бизнес-логики.
Комплексная функция для извлечения полной информации:
function deepExtract(errors) {
const result = [];
const walk = (errs, path = "") => {
for (const e of errs) {
const currentPath = path ? `${path}.${e.property}` : e.property;
if (e.constraints) {
for (const [type, message] of Object.entries(e.constraints)) {
result.push({
path: currentPath,
type,
message
});
}
}
if (e.children?.length) {
walk(e.children, currentPath);
}
}
};
walk(errors);
return result;
}
Такой подход превращает дерево ошибок в плоскую диагностическую таблицу, пригодную для логирования, API-ответов и аналитики.