Kurs NestJS · Moduł 10: Walidacja danych
Walidacja zagnieżdżona - Inspekcja głęboka
W tej lekcji5
Dotychczas walidowaliśmy proste, płaskie obiekty. Ale co jeśli przychodzące dane mają zagnieżdżone obiekty lub tablice obiektów? Potrzebujemy głębokiej inspekcji - strażnicy muszą sprawdzić nie tylko osobę na bramie, ale też jej bagaż, dokumenty w torbie i przedmioty w kieszeniach.
Problem - płaska walidacja nie wystarczy
Wyobraź sobie DTO dla legionu, który zawiera listę legionistów:
1// BEZ zagnieżdżonej walidacji
2export class CreateLegioDto {
3 @IsString()
4 name: string;
5
6 @IsArray()
7 soldiers: CreateSoldierDto[]; // Tablica obiektów
8 // Ale czy każdy obiekt w tablicy jest poprawny? NIE WIEMY!
9}Samo @IsArray() sprawdzi tylko czy wartość jest tablicą - nie zwaliduje zawartości obiektów wewnątrz!
@ValidateNested() - strażnik zagnieżdżonych obiektów
Dekorator @ValidateNested() mówi class-validator: "Sprawdź też walidację obiektu wewnętrznego":
1import { ValidateNested } from 'class-validator';
2import { Type } from 'class-transformer';
3
4class CreateSoldierDto {
5 @IsString()
6 @IsNotEmpty()
7 name: string;
8
9 @IsNumber()
10 @Min(16)
11 age: number;
12}
13
14export class CreateLegioDto {
15 @IsString()
16 @IsNotEmpty()
17 name: string;
18
19 @ValidateNested()
20 @Type(() => CreateSoldierDto)
21 commander: CreateSoldierDto;
22}Ważne: @ValidateNested() zawsze wymaga @Type() z class-transformer! Bez niego NestJS nie wie, na jaką klasę skonwertować surowy obiekt.
Walidacja tablic zagnieżdżonych obiektów
Gdy masz tablicę obiektów, musisz dodać { each: true }:
1export class CreateLegioDto {
2 @IsString()
3 name: string;
4
5 @ValidateNested({ each: true })
6 @Type(() => CreateSoldierDto)
7 soldiers: CreateSoldierDto[];
8}Parametr { each: true } mówi dekoratorowi, żeby sprawdził każdy element tablicy osobno. Bez niego walidacja zagnieżdżona nie zadziała na tablicach.
Wielopoziomowe zagnieżdżenie
Zagnieżdżenie może sięgać wielu poziomów głębokości:
1class WeaponDto {
2 @IsString()
3 @IsNotEmpty()
4 name: string;
5
6 @IsNumber()
7 @Min(1)
8 damage: number;
9}
10
11class SoldierDto {
12 @IsString()
13 @IsNotEmpty()
14 name: string;
15
16 @ValidateNested()
17 @Type(() => WeaponDto)
18 primaryWeapon: WeaponDto;
19
20 @IsOptional()
21 @ValidateNested()
22 @Type(() => WeaponDto)
23 secondaryWeapon?: WeaponDto;
24}
25
26class CohortDto {
27 @IsString()
28 name: string;
29
30 @ValidateNested({ each: true })
31 @Type(() => SoldierDto)
32 soldiers: SoldierDto[];
33}
34
35export class CreateLegioDto {
36 @IsString()
37 name: string;
38
39 @ValidateNested({ each: true })
40 @Type(() => CohortDto)
41 cohorts: CohortDto[];
42}W tym przykładzie NestJS zwaliduje:
- Czy
CreateLegioDto.cohortsto tablica poprawnychCohortDto - Czy każdy
CohortDto.soldiersto tablica poprawnychSoldierDto - Czy każdy
SoldierDto.primaryWeaponto poprawnyWeaponDto
Komunikaty błędów zagnieżdżonych
Gdy walidacja zagnieżdżona nie przejdzie, NestJS zwróci szczegółowy błąd ze ścieżką do problematycznego pola:
1// Przykładowa odpowiedź błędu:
2{
3 "statusCode": 400,
4 "message": [
5 "cohorts.0.soldiers.1.name should not be empty",
6 "cohorts.0.soldiers.1.primaryWeapon.damage must be at least 1"
7 ],
8 "error": "Bad Request"
9}Głęboka inspekcja to kluczowa umiejętność strażników bramy. Bez niej złośliwy użytkownik mógłby przemycić niepoprawne dane w zagnieżdżonych obiektach!
Kod do tej lekcji: src/nested-validation.ts
1// Walidacja zagnieżdżona - Głęboka inspekcja
2import {
3 IsString,
4 IsNotEmpty,
5 IsNumber,
6 Min,
7 Max,
8 IsArray,
9 ArrayMinSize,
10 ValidateNested,
11 IsOptional,
12 IsEnum,
13} from 'class-validator';
14import { Type } from 'class-transformer';
15
16// DTO broni
17class WeaponDto {
18 @IsString()
19 @IsNotEmpty()
20 name: string;
21
22 @IsNumber()
23 @Min(1)
24 @Max(100)
25 damage: number;
26}
27
28// TODO: Uzupełnij DTO żołnierza z zagnieżdżoną walidacją
29class SoldierDto {
30 @IsString()
31 @IsNotEmpty()
32 name: string;
33
34 @IsNumber()
35 @Min(16)
36 age: number;
37
38 // TODO: Dodaj @ValidateNested() i @Type(() => WeaponDto)
39 primaryWeapon: WeaponDto;
40
41 // TODO: Dodaj @IsOptional(), @ValidateNested(), @Type(() => WeaponDto)
42 secondaryWeapon?: WeaponDto;
43}
44
45// TODO: Uzupełnij DTO kohorty z tablicą zagnieżdżonych obiektów
46class CohortDto {
47 @IsString()
48 @IsNotEmpty()
49 name: string;
50
51 // TODO: Dodaj @ValidateNested({ each: true })
52 // TODO: Dodaj @Type(() => SoldierDto)
53 // TODO: Dodaj @IsArray() i @ArrayMinSize(3)
54 soldiers: SoldierDto[];
55}
56
57console.log('Zagnieżdżona walidacja sprawdza obiekty w głąb!');
58console.log('Pamiętaj: @ValidateNested wymaga @Type!');
59Widzisz błąd w tej lekcji?
Sprawdź się
Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.
1. Co robi dekorator @ValidateNested() w class-validator?
2. Dlaczego @ValidateNested() wymaga użycia @Type() z class-transformer?
To 2 z 3 pytań do tej lekcji. Pozostałe rozwiążesz w grze.
Zadania praktyczne w grze
- Edytor kodu
Uzupełnij dekoratory walidacji zagnieżdżonej w SoldierDto i CohortDto
- Układanie w pionie
Uporządkuj poziomy walidacji zagnieżdżonej od zewnętrznego do wewnętrznego:
- Klikanie w kolejności
Ułóż dekoratory pola soldiers w CreateLegioDto w prawidłowej kolejności: