Kurs NestJS · Moduł 10: Walidacja danych

Walidacja zagnieżdżona - Inspekcja głęboka

2 min czytania
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.cohorts to tablica poprawnych CohortDto
  • Czy każdy CohortDto.soldiers to tablica poprawnych SoldierDto
  • Czy każdy SoldierDto.primaryWeapon to poprawny WeaponDto

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!');
59

Widzisz błąd w tej lekcji?

Sprawdź się

Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.

  1. 1. Co robi dekorator @ValidateNested() w class-validator?

  2. 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:

Przydatne artykuły