IlmHamroh
JavaScript Full-stack/30-qism. NestJS17/65-dars21 daqiqa
Mundarija (33)

Mapped types va nested validatsiya: PartialType, @ValidateNested, @Type va guruhlar

Qisqacha: Mapped types — mavjud DTO'dan yangisini yasaydigan funksiyalar: PartialType(YangiDto) — hamma maydon ixtiyoriy, PickType — faqat tanlanganlar, OmitType — tanlanganlarsiz, IntersectionType — ikkitasi birga. Qoidalar meros bo'lib o'tadi. Ichma-ich obyekt yoki massiv elementlarini tekshirish uchun ikkita dekorator kerak: @ValidateNested() va @Type(() => Element). @Type ni unutsangiz, ichki element umuman tekshirilmaydi — xatosiz. Bitta DTO'ni turli amallarda turlicha tekshirish — validatsiya guruhlari (groups).

Bu darsda

  • PartialType, PickType, OmitType, IntersectionType bilan DTO'larni takrorsiz yasaysiz.
  • PartialType ning ikki tuzog'ini (null va bo'sh {}) haqiqiy javobda ko'rasiz.
  • Ichma-ich obyektlar va massivlarni @ValidateNested + @Type bilan tekshirasiz va @Type siz nima bo'lishini bilasiz.
  • Validatsiya guruhlari bilan bitta DTO'ni yaratish va yangilashda turlicha tekshirasiz.
  • class-validator variantining yakuniy natijasini — 52/52 test va 33 so'rovli solishtiruvni — o'qiysiz.

Oldin bilishingiz kerak: DTO va class-validator, ValidationPipe sozlamalari va tuzoqlari, Utility types, Bulk va uzoq davom etuvchi operatsiyalar.

O'tgan darsda ValidationPipe sozlamalarini tanladik. Endi DTO'larning o'ziga qaytamiz: ular ko'paygan sari takrorlanadi va ichma-ich bo'ladi.

1. Nega bu kerak?

vazifalar-api da bitta vazifa uchun uch xil kirish bor: POST (matn majburiy), PUT (matn va bajarildi majburiy), PATCH (ikkalasi ixtiyoriy). Har biriga DTO alohida yozilsa — matn qoidalari (@IsString, @Length, @Transform) uch joyda takrorlanadi. Ertaga uzunlik 200 ga o'zgarsa — uch joyni topish kerak, birini unutish oson.

TypeScript'da bu muammoni Utility types hal qiladi: Partial<T>, Pick<T, K>, Omit<T, K>. Lekin ular faqat tur yasaydi — ish vaqtida yo'qoladi va class-validator qoidalarini ko'chirmaydi. Nest'ning mapped types'i — xuddi shu g'oya, lekin klass yasaydi va qoidalarni ham olib o'tadi.

Ikkinchi muammo — ichma-ich ma'lumot. PUT /v1/vazifalar butun ro'yxatni oladi: {"vazifalar": [{...}, {...}]}. Har elementni tekshirish kerak. class-validator buni qila oladi, lekin bitta muhim shart bilan — u bu darsning eng xavfli tuzog'i.

O'xshatish: kiyim tikuvchisining andozasi. Har ko'ylak uchun noldan chizish o'rniga, bitta asosiy andozadan "yengsiz", "kalta", "kattaroq" variantlar qirqiladi. Mapped types — andozani qirqish; asosiy andozadagi o'lchamlar (qoidalar) har variantga o'tadi.

2. Mapped types

2.1 To'rt funksiya

Ular @nestjs/mapped-types paketida (biz sinagan versiya — 12.0.0). Swagger ishlatilsa, xuddi shu funksiyalar @nestjs/swagger dan import qilinadi — ular qo'shimcha ravishda API hujjati metama'lumotini ham ko'chiradi (Swagger). Nest CLI ning nest g resource buyrug'i @nestjs/mapped-types ni o'zi qo'shadi.

ts
class YangiDto {
  @IsString() @Length(1, 100) matn!: string;
  @IsOptional() @IsBoolean() bajarildi?: boolean;
}
class FiltrDto {
  @IsOptional() @IsBoolean() bajarildi?: boolean;
  @IsOptional() @IsInt() limit?: number;
}

class OzgarishDto extends PartialType(YangiDto) {}
class FaqatMatn extends PickType(YangiDto, ["matn"] as const) {}
class MatnsizDto extends OmitType(YangiDto, ["matn"] as const) {}
class Birlashgan extends IntersectionType(FaqatMatn, FiltrDto) {}
Funksiya TypeScript'dagi juftligi Natija
PartialType(A) Partial<A> A ning hamma maydoni, hammasi ixtiyoriy
PickType(A, ["x"]) Pick<A, "x"> faqat x
OmitType(A, ["x"]) Omit<A, "x"> x dan boshqasi
IntersectionType(A, B) A & B ikkalasining maydonlari

["matn"] as const — as const maydon nomini string emas, aniq "matn" turi qiladi; usiz TypeScript qaysi maydonlar qolishini bilmaydi.

Funksiya klass qaytaradi, shuning uchun extends bilan yoziladi. Natija oddiy DTO — unga yangi maydon qo'shish ham mumkin.

2.2 Ichma-ich chaqirish

Funksiya klass qaytargani uchun ularni bir-birining ichiga qo'yish mumkin. Masalan, PATCH'da bajarildi umuman o'zgartirilmasin, matn esa ixtiyoriy bo'lsin:

ts
class MatnOzgarishiDto extends PartialType(
  OmitType(YangiDto, ["bajarildi"] as const),
) {}

Ichkaridan tashqariga o'qiladi: avval OmitType bajarildi ni olib tashlaydi, keyin PartialType qolgan maydonlarni ixtiyoriy qiladi. Natija — faqat ixtiyoriy matn, uning qoidalari bilan. Bu qatorni oddiy so'z bilan aytish oson: "yangi vazifa shaklidan bajarildi siz, hammasi ixtiyoriy".

Demo loyihada uch kirish bilan sinadik — validate() ga whitelist va forbidNonWhitelisted berib (biz ishga tushirdik: @nestjs/mapped-types 12.0.0, class-validator 0.15.1, Node 24.21.0):

text
{} → []
{"matn":""} → [{"isLength":"matn must be longer than or equal to 1 characters"}]
{"bajarildi":"ha"} → [{"whitelistValidation":"property bajarildi should not exist"}]

Nima bo'ldi? Bo'sh obyekt o'tdi — hamma maydon ixtiyoriy. Bo'sh matn rad etildi — @Length meros bo'lib o'tgan. bajarildi esa endi DTO'da yo'q: uning qoidasi (@IsBoolean) emas, "bu maydon bo'lmasligi kerak" xabari chiqdi. Ya'ni OmitType maydonni olib tashladi, taqiqni esa forbidNonWhitelisted qo'ydi.

Natija kutilganidek: har funksiya o'z ishini qildi va meros qoidalar yo'qolmadi — ichma-ich chaqiruv xavfsiz. Bir ehtiyot: uch-to'rt qavat ichma-ich chaqiruv o'qishni qiyinlashtiradi. Bunday DTO'ga ism bering va yoniga bir qatorlik izoh yozing — u qaysi amal uchun.

2.3 Zod'da xuddi shu g'oya

25-qismda bu muammoni Zod'ning o'z metodlari hal qilgandi. Mapped types — ularning klassli egizagi:

Nest mapped types Zod 4
PartialType(A) A.partial()
PickType(A, ["matn"]) A.pick({ matn: true })
OmitType(A, ["id"]) A.omit({ id: true })
IntersectionType(A, B) A.extend(B.shape)

Bitta muhim farq bor. Zod'ning .partial() maydonni undefined ga ruxsat beradi, lekin null ga — yo'q. PartialType esa @IsOptional() qo'yadi va u null ni ham o'tkazadi. Keyingi bo'limdagi birinchi tuzoq aynan shu farqdan kelib chiqadi.

Tekshirib ko'ring: class A extends PickType(YangiDto, ["matn"] as const) {} va Zod'da YangiVazifa.pick({ matn: true }). Ikkalasiga {"matn": "Non", "bajarildi": true} berildi (class-validator'da forbidNonWhitelisted yoqilgan, Zod sxemasi — strictObject). Natijalar bir xilmi?

Javob

Ha, ikkalasi ham rad etadi: bajarildi endi shaklda yo'q, qat'iy rejimda bu xato. class-validator xabari — "property bajarildi should not exist", Zod'niki — "Unrecognized key". Farq faqat xabar matnida. Zod'da pick qilingan strictObject qat'iyligini saqlaydi; class-validator'da esa qat'iylik DTO'da emas, pipe sozlamasida.

2.4 Qanday ishlaydi

Mapped types funksiyasi yangi bo'sh klass yasaydi va asl klassning metama'lumotidan kerakli maydonlarni ko'chiradi: class-validator qoidalari, class-transformer sozlamalari va TypeScript turlari. PartialType qo'shimcha ravishda har maydonga @IsOptional() qo'yadi.

flowchart LR
  A["YangiDto<br/>matn: IsString, Length<br/>bajarildi: IsOptional, IsBoolean"] --> P["PartialType"]
  P --> B["OzgarishDto<br/>matn: + IsOptional<br/>bajarildi: IsOptional"]
  A --> O["OmitType 'matn'"]
  O --> C["MatnsizDto<br/>bajarildi"]

Diagrammaga qarang: OzgarishDto ning matn maydonida eski qoidalar qoladi, ustiga IsOptional qo'shiladi. Demak {"matn": ""} hali ham rad etiladi, {} esa o'tadi.

2.5 Haqiqiy natijalar

Demo loyihada (biz ishga tushirdik: NestJS 12.2.0, @nestjs/mapped-types 12.0.0, class-validator 0.15.1) Nest'siz, to'g'ridan-to'g'ri validate() bilan:

text
3) PartialType va PickType:
  OzgarishDto {"muhimlik":2} → []
  OzgarishDto {"muhimlik":"x"} → ["muhimlik: muhimlik must be an integer number"]
  MatnDto {"matn":""} → ["matn: matn must be longer than or equal to 1 characters"]

(Bu demoda YangiDto ga muhimlik maydoni qo'shilgan, MatnDto — PickType(YangiDto, ["matn"]).) OzgarishDto uchun matn majburiy emas — {"muhimlik":2} o'tdi. Lekin muhimlik qoidasi qoldi. MatnDto @Length ni meros oldi.

Kanon tajribasida esa xuddi shu DTO'lar Nest controller'i orqali, oltita ValidationPipe sozlamasi bilan yurgizilgan (o'tgan darsdagi A–F; kurs tajribasi, natijalar Vitest ichida). OmitType va IntersectionType uchun:

text
8) OmitType(matn): matn berilsa  /matnsiz {"matn":"x","bajarildi":true}
  B) new ValidationPipe() — sukut            → 201 {"qiymat":{"matn":"x","bajarildi":true},"klass":"Object","turlar":{"matn":"string","bajarildi":"boolean"}}
  C) whitelist: true                         → 201 {"qiymat":{"bajarildi":true},"klass":"Object","turlar":{"bajarildi":"boolean"}}
  D) whitelist + forbidNonWhitelisted        → 400 {"message":["property matn should not exist"],"error":"Bad Request","statusCode":400}

9) IntersectionType(Pick matn, Filtr): limit "5"  /birlashgan {"matn":"x","limit":"5"}
  B) new ValidationPipe() — sukut            → 400 {"message":["limit must be an integer number"],"error":"Bad Request","statusCode":400}
  F) transform + enableImplicitConversion    → 201 {"qiymat":{"matn":"x","limit":5},"klass":"Birlashgan","turlar":{"matn":"string","limit":"number"}}

OmitType dan keyin matn — DTO'da yo'q maydon. Sukutda u o'tadi (B), whitelist — tashlaydi (C), forbidNonWhitelisted — rad etadi (D). Ya'ni OmitType maydonni "taqiqlamaydi", faqat DTO'dan chiqaradi; taqiq — whitelist sozlamasining ishi. IntersectionType ikkala klassning qoidalarini olgan: limit uchun @IsInt ishladi.

3. PartialType ning ikki tuzog'i

3.1 null o'tadi

PATCH — JSON Merge Patch: {"matn": null} "matnni o'chir" degani (HTTP metodlari semantikasi). matn majburiy maydon — uni o'chirib bo'lmaydi, shuning uchun 422 kutamiz. Kanon tajribasi:

text
6) PartialType: {"matn": null}  /ozgarish {"matn":null}
  B) new ValidationPipe() — sukut            → 201 {"qiymat":{"matn":null},"klass":"Object","turlar":{"matn":"object"}}
  D) whitelist + forbidNonWhitelisted        → 201 {"qiymat":{"matn":null},"klass":"Object","turlar":{"matn":"object"}}
  E) transform: true                         → 201 {"qiymat":{"matn":null},"klass":"OzgarishDto","turlar":{"matn":"object"}}

Hamma sozlamada 201. Sabab — PartialType qo'ygan @IsOptional(): u null ni ham "qiymat yo'q" deb hisoblaydi va qolgan qoidalarni o'tkazib yuboradi (DTO va class-validator darsidagi ogohlantirish). Handler matn: null oladi; ombor NOT NULL cheklovi bo'lsa — 500, bo'lmasa — matnsiz vazifa.

3.2 Bo'sh {} o'tadi

text
7) PartialType: {} (bo'sh o'zgarish)  /ozgarish {}
  B) new ValidationPipe() — sukut            → 201 {"qiymat":{},"klass":"Object","turlar":{}}
  D) whitelist + forbidNonWhitelisted        → 201 {"qiymat":{},"klass":"Object","turlar":{}}

Bo'sh PATCH — "hech narsani o'zgartirma". 25-qismda biz uni 422 qildik: "o'zgarish yo'q: matn yoki bajarildi". Bunday so'rov ko'pincha mijoz xatosi. class-validator'da obyekt darajasidagi qoida ("kamida bitta maydon") uchun tayyor dekorator yo'q — har dekorator bitta maydonga bog'langan.

3.3 Yechim

Kanon varianti PartialType ishlatmagan — OzgarishDto ni qo'lda yozgan (DTO va class-validator darsidagi «Loyiha qadami»):

  • null uchun — @ValidateIf((o) => o.matn !== undefined) @IsOptional() o'rniga: "maydon berilgan bo'lsa — null bo'lsa ham — tekshir".
  • {} uchun — controller'da kichik tekshiruv.

Ya'ni mapped types takrorni kamaytiradi, lekin PATCH semantikasi uchun har doim ham to'g'ri DTO bermaydi. Qaror: PartialType ni ishlating, agar null sizda "yo'q" bilan bir xil ma'noda bo'lsa; aks holda — qo'lda.

Tekshirib ko'ring: class OzgarishDto extends PartialType(YangiDto). Mijoz {"bajarildi": "ha"} yubordi, forbidNonWhitelisted yoqilgan. Javob qanday?

Javob

400: "bajarildi must be a boolean value". PartialType faqat @IsOptional() qo'shadi — qiymat berilgan bo'lsa (va null/undefined bo'lmasa), asl qoidalar ishlaydi. "ha" — satr, @IsBoolean uni rad etadi.

4. Ichma-ich DTO'lar

4.1 @ValidateNested va @Type

Ichma-ich obyektni tekshirish uchun ikki dekorator kerak:

ts
class Element { @IsString() matn!: string; }

class TypeliToplam {
  @IsArray()
  @ValidateNested({ each: true })
  @Type(() => Element)
  vazifalar!: Element[];
}
class TypesizToplam {
  @IsArray()
  @ValidateNested({ each: true })
  vazifalar!: Element[];
}
  • @ValidateNested() — class-validator'ga: "bu maydonning ichiga ham kir".
  • @Type(() => Element) — class-transformer'ga: "bu maydondagi oddiy obyektlarni Element nusxasiga aylantir".

Nega ikkalasi? DTO va class-validator darsidagi ikki qadamni eslang: qoidalar klass nusxasida topiladi. plainToInstance(TypeliToplam, ...) tashqi obyektni nusxaga aylantiradi. Lekin vazifalar ichidagi elementlar haqida u hech narsa bilmaydi — TypeScript'ning Element[] turi ish vaqtida Array dan boshqa ma'lumot qoldirmaydi. @Type aynan shu bo'shliqni to'ldiradi.

4.2 @Type siz — tekshiruv yo'q

So'rov {"vazifalar":[{"matn":1}]} — element yaroqsiz (matn son). Kanon tajribasi:

text
4) nested @Type BILAN, element yaroqsiz  /typeli {"vazifalar":[{"matn":1}]}
  B) new ValidationPipe() — sukut            → 400 {"message":["vazifalar.0.matn must be a string"],"error":"Bad Request","statusCode":400}

5) nested @Type'SIZ, element yaroqsiz  /typesiz {"vazifalar":[{"matn":1}]}
  B) new ValidationPipe() — sukut            → 201 {"qiymat":{"vazifalar":[{"matn":1}]},"klass":"Object","turlar":{"vazifalar":"object"}}
  C) whitelist: true                         → 201 {"qiymat":{"vazifalar":[{}]},"klass":"Object","turlar":{"vazifalar":"object"}}
  D) whitelist + forbidNonWhitelisted        → 400 {"message":["vazifalar.0.property matn should not exist"],"error":"Bad Request","statusCode":400}
  F) transform + enableImplicitConversion    → 201 {"qiymat":{"vazifalar":[{"matn":1}]},"klass":"TypesizToplam","turlar":{"vazifalar":"object"}}

@Type bilan — to'g'ri xato: "vazifalar.0.matn satr bo'lishi kerak". Yo'l (vazifalar.0.matn) aniq ko'rsatilgan.

@Type siz — uch xil yomon natija:

  • B — sukut: 201. Element umuman tekshirilmadi, {"matn": 1} handler'ga yetdi.
  • C — whitelist: 201, element bo'sh {} bo'lib qoldi. class-validator element klassini bilmaydi — demak "ruxsat etilgan maydon" ham yo'q, hammasini olib tashladi. Mijozning ma'lumoti jim yo'qoldi.
  • D — forbidNonWhitelisted: 400, lekin noto'g'ri sabab: "vazifalar.0 da matn maydoni bo'lmasligi kerak". Mijoz matn ni to'g'ri yuborgan; xabar uni adashtiradi.

F qatorida yana bir g'alati natija bor: element o'zgarishsiz o'tdi. Bu holatlarning hech biri ogohlantirish bermaydi. Shuning uchun qoida qat'iy: @ValidateNested bor joyda @Type ham bo'lsin. Ko'p jamoalar buni lint qoidasi yoki code review ro'yxatiga kiritadi.

4.3 Massiv elementlari: each

Oddiy qiymatlar massivi uchun ichma-ich klass shart emas — har dekoratorning { each: true } sozlamasi yetadi:

ts
class Manzil {
  @IsString() shahar!: string;
  @Matches(/^\d{6}$/, { message: 'indeks — 6 raqam' })
  indeks!: string;
}
class BuyurtmaDto {
  @ValidateNested() @Type(() => Manzil) manzil!: Manzil;

  @IsArray()
  @ArrayMaxSize(3)
  @IsString({ each: true })
  @Length(1, 10, { each: true })
  teglar!: string[];
}

{"manzil":{"shahar":"Toshkent","indeks":"1000"},"teglar":["tez","","juda-uzun-teg-nomi","x"]} uchun demo natijasi:

text
2) nested obyekt va massiv:
  [
 "manzil.indeks: indeks — 6 raqam",
 "teglar: each value in teglar must be longer than or equal to 1 and shorter than or equal to 10 characters",
 "teglar: teglar must contain no more than 3 elements"
]

(Chiqishda xatolarni property bo'yicha yo'l qilib yig'dik: manzil.indeks.) Uchta xato:

  • manzil.indeks — bitta ichma-ich obyekt (@ValidateNested() each siz).
  • teglar uzunligi — "teglar dagi har qiymat 1–10 belgi bo'lishi kerak". E'tibor bering: qaysi element ("" yoki "juda-uzun-teg-nomi") aytilmagan — each xabari butun massiv uchun bitta.
  • teglar soni — 4 ta, ruxsat 3.

Diqqat: @ArrayMaxSize — himoya, bezak emas. Usiz {"teglar": [... 100 000 ta ...]} tekshiruvining o'zi serverni band qiladi — har element uchun har qoida ishlaydi.

5. Validatsiya guruhlari

5.1 Bitta DTO — ikki qoida to'plami

Ba'zan bitta DTO turli amallarda turlicha tekshiriladi: yaratishda id yo'q, yangilashda — majburiy. Har qoidaga groups beriladi:

ts
class VazifaDto {
  @IsInt({ groups: ['yangilash'] })
  id!: number;

  @IsString({ always: true })
  @Length(1, 100, { always: true })
  matn!: string;
}
  • groups: ['yangilash'] — qoida faqat shu guruh tekshirilganda.
  • always: true — qoida har qanday guruhda.

{"id":"x","matn":""} ni uch xil chaqirdik:

text
1) guruhlar:
  guruhsiz: ["id: id must be an integer number","matn: matn must be longer than or equal to 1 characters"]
  yaratish: ["matn: matn must be longer than or equal to 1 characters"]
  yangilash: ["id: id must be an integer number","matn: matn must be longer than or equal to 1 characters"]

yaratish guruhida id tekshirilmadi, yangilash da — tekshirildi. Kutilganidek.

5.2 Guruhsiz chaqiruv tuzog'i

Birinchi qatorga qarang: guruhsiz chaqiruvda id qoidasi ham ishladi, garchi u faqat yangilash guruhiga yozilgan bo'lsa. class-validator sukut bo'yicha guruhsiz chaqiruvda hamma qoidalarni bajaradi. Buni strictGroups: true sozlamasi o'zgartiradi — shunda guruhli qoidalar faqat o'z guruhida ishlaydi.

Nest'da guruh ValidationPipe ga beriladi: new ValidationPipe({ groups: ['yangilash'] }) — odatda parametr darajasida (@Body(new ValidationPipe({ groups: [...] }))), chunki har endpointning guruhi boshqa.

Amalda guruhlar kam ishlatiladi: ikki alohida DTO (yoki PickType/OmitType) o'qishga osonroq, xatolari ko'rinadigan bo'ladi. Guruhlar — bitta DTO'da qoidalar juda ko'p va ular deyarli bir xil bo'lganda.

Tekshirib ko'ring: @IsInt({ groups: ['yangilash'] }) id bor DTO global ValidationPipe() (guruhsiz) bilan POST'da ishlatildi. Mijoz id yubormadi. Nima bo'ladi?

Javob

400: "id must be an integer number". Guruhsiz chaqiruvda guruhli qoida ham ishlaydi (strictGroups sukutda false) va id yo'qligi @IsInt ni buzadi. Mijoz "yaratishda id kerak emas-ku" deb hayron bo'ladi. Yechim — POST'da groups: ['yaratish'] bilan pipe, strictGroups: true yoki alohida DTO.

6. Hujumchi nigohi

  1. @Type siz ichma-ich maydon. Massiv elementi tekshirilmaydi — {"vazifalar":[{"matn":{"$gt":""},"egasi_id":1}]} handler'ga yetadi. Himoya — @ValidateNested + @Type juftligi va har element uchun forbidNonWhitelisted.
  2. Ulkan massiv. Elementlar soni cheklanmasa, tekshiruvning o'zi CPU'ni band qiladi. Himoya — @ArrayMaxSize (vazifalar-api da — 1000 va 100).
  3. Chuqur ichma-ichlik. Har daraja — yana plainToInstance va validate. Himoya — tana hajmi chegarasi (express.json({ limit: "16kb" })) va DTO tuzilmasini cheklash; rekursiv DTO'dan qoching.
  4. PATCH bilan majburiy maydonni o'chirish. PartialType + {"matn": null} — tekshiruvdan o'tadi. Himoya — @ValidateIf(o => o.matn !== undefined) yoki ombor darajasidagi NOT NULL va to'g'ri xato xaritasi.

7. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
Ichki element tekshirilmaydi @Type(() => X) yo'q @ValidateNested + @Type
whitelist bilan element {} bo'lib qoldi @Type yo'q @Type(() => X)
vazifalar.0.property matn should not exist @Type yo'q + forbidNonWhitelisted @Type(() => X)
PATCH'da {"matn": null} o'tadi PartialType → @IsOptional @ValidateIf(... !== undefined)
Guruhli qoida kutilmagan joyda ishladi guruhsiz chaqiruv strictGroups: true yoki guruh berish
PickType(A, ["x"]) da tur xatosi as const yo'q ["x"] as const

8. Mashqlar

Mashqlar kurs/mashqlar/30/17-mapped-types/ da.

1-mashq (oson): qaysi funksiya

  • YangiDto ning hamma maydoni ixtiyoriy — (YangiDto)
  • Faqat email va parol — (FoydalanuvchiDto, ["email", "parol"] as const)
  • parolHash dan boshqa hammasi — (Foydalanuvchi, ["parolHash"] as const)
  • Ichki elementlarni klassga aylantiradigan dekorator — @(() => Element)
  • Ichki elementga kirib tekshiradigan dekorator — @()
Yechim
  • PartialType — har maydonga @IsOptional() qo'shadi (va null ni ham o'tkazadi).
  • PickType — tanlangan maydonlar va ularning qoidalari.
  • OmitType — qolgan maydonlar; olib tashlangan maydonni taqiqlash — forbidNonWhitelisted.
  • Type — class-transformer'dan; usiz ichki qoidalar topilmaydi.
  • ValidateNested — class-validator'dan; massiv uchun { each: true }.

2-mashq (o'rta): PartialType modeli

DTO qoidalari oddiy obyekt sifatida berilgan: { maydon: [qoidalar...] }. Har qoida — { nomi, tekshir }. qisman(qoidalar) funksiyasini yozing — u PartialType kabi yangi obyekt qaytarsin: har maydon boshiga { nomi: "ixtiyoriy" } qo'shilsin, asl obyekt o'zgarmasin. tekshir(qoidalar, qiymat) esa ixtiyoriy maydon undefined yoki null bo'lsa uning qoidalarini o'tkazib yuborsin. Ishora: Object.fromEntries va spread (Spread va rest).

Yechim
js
const satr = { nomi: "satr", tekshir: (v) => typeof v === "string" };
const bosh = {
  nomi: "bo'sh emas",
  tekshir: (v) => typeof v === "string" && v.length > 0,
};
const yangi = { matn: [satr, bosh] };

function qisman(qoidalar) {
  return Object.fromEntries(
    Object.entries(qoidalar).map(([m, q]) => [
      m, [{ nomi: "ixtiyoriy" }, ...q],
    ]),
  );
}

function tekshir(qoidalar, qiymat) {
  const xatolar = [];
  for (const [m, royxat] of Object.entries(qoidalar)) {
    const v = qiymat[m];
    const ixtiyoriy = royxat.some((q) => q.nomi === "ixtiyoriy");
    if (ixtiyoriy && (v === undefined || v === null)) continue;
    for (const q of royxat) {
      if (q.tekshir && !q.tekshir(v)) xatolar.push(`${m}: ${q.nomi}`);
    }
  }
  return xatolar;
}

const ozgarish = qisman(yangi);
console.log(tekshir(yangi, {}));
console.log(tekshir(ozgarish, {}));
console.log(tekshir(ozgarish, { matn: null }));
console.log(tekshir(ozgarish, { matn: "" }));
console.log(yangi.matn.length, ozgarish.matn.length);

Konsolda:

text
[ 'matn: satr', "matn: bo'sh emas" ]
[]
[]
[ "matn: bo'sh emas" ]
2 3

Uchinchi qator — PartialType ning null tuzog'i aynan: { matn: null } xatosiz o'tdi. To'rtinchi — asl qoidalar qoldi: bo'sh satr rad etildi. Oxirgisi — asl yangi o'zgarmadi (2 qoida), yangisida 3. Haqiqiy mapped types ham asl klassga tegmaydi.

3-mashq (qiyin): ichma-ich tekshiruv

tekshirIchma(sxema, qiymat, yol) funksiyasini yozing. sxema — { maydon: tur }, tur — "satr", "son" yoki { massiv: <ichki sxema> }. Massivda har element ichki sxema bo'yicha tekshirilsin, xato yo'li vazifalar.1.matn ko'rinishida. Element obyekt bo'lmasa — vazifalar.2: obyekt kutilgan. Bu @ValidateNested + @Type ning modeli. Ishora: Array.isArray, rekursiya (Rekursiya asoslari).

Yechim
js
function tekshirIchma(sxema, qiymat, yol, xatolar = []) {
  for (const [m, tur] of Object.entries(sxema)) {
    const v = qiymat[m];
    const joy = yol ? `${yol}.${m}` : m;
    if (tur === "satr" && typeof v !== "string") {
      xatolar.push(`${joy}: satr kutilgan`);
    } else if (tur === "son" && typeof v !== "number") {
      xatolar.push(`${joy}: son kutilgan`);
    } else if (typeof tur === "object") {
      if (!Array.isArray(v)) {
        xatolar.push(`${joy}: massiv kutilgan`);
        continue;
      }
      v.forEach((el, i) => {
        if (typeof el !== "object" || el === null) {
          xatolar.push(`${joy}.${i}: obyekt kutilgan`);
        } else {
          tekshirIchma(tur.massiv, el, `${joy}.${i}`, xatolar);
        }
      });
    }
  }
  return xatolar;
}

const sxema = { vazifalar: { massiv: { matn: "satr" } } };
console.log(tekshirIchma(sxema, {
  vazifalar: [{ matn: "ok" }, { matn: 1 }, 5],
}, ""));
console.log(tekshirIchma(sxema, { vazifalar: "x" }, ""));

Konsolda:

text
[ 'vazifalar.1.matn: satr kutilgan', 'vazifalar.2: obyekt kutilgan' ]
[ 'vazifalar: massiv kutilgan' ]

Yo'llar class-validator'nikiga o'xshaydi (vazifalar.1.matn). Agar bu funksiyada massiv tarmog'ini olib tashlasangiz (typeof tur === "object" holati), elementlar umuman tekshirilmaydi — @Type siz class-validator'ning xulqi.

9. Loyiha qadami: variantning ichma-ich DTO'lari va yakuniy natija (loyihaga kirmaydi)

Bu qadam tajriba/class-validator tarmog'ida. nest tarmog'ida o'zgarish yo'q.

9.1 ToplamDto va BulkDto

vazifa.dto.ts ning oxiriga uchta klass qo'shiladi. ToplamElementDto — ToliqVazifaDto dan oddiy extends bilan meros oladi va id qo'shadi; IntersectionType ham mumkin edi, lekin bitta maydon uchun oddiy meros o'qishga osonroq. Qatorlar telefon uchun bo'lingan; nusxani tsc --noEmit bilan tekshirdik.

ts
// vazifa.dto.ts (davomi) — import qatoriga ArrayMaxSize,
// ArrayMinSize, IsArray, IsInt, IsPositive, ValidateNested va
// class-transformer'dan Type qo'shiladi; MAKS_TOPLAM — sxema.ts dan

/** PUT /v1/vazifalar elementi: ToliqVazifa + ixtiyoriy id
 * (mapped types'siz meros) */
export class ToplamElementDto extends ToliqVazifaDto {
  @IsOptional()
  @IsPositive({ message: "id — musbat butun son" })
  @IsInt({ message: "id — musbat butun son" })
  id?: number;
}

/** PUT /v1/vazifalar — ichma-ich: @ValidateNested + @Type
 * (har element ToplamElementDto ga aylansin) */
export class ToplamDto {
  @ValidateNested({ each: true })
  @Type(() => ToplamElementDto)
  @ArrayMaxSize(MAKS_TOPLAM, {
    message: `vazifalar — ko'pi bilan ${String(MAKS_TOPLAM)} ta`,
  })
  @IsArray({ message: "vazifalar — massiv" })
  vazifalar!: ToplamElementDto[];
}

/** POST /toplam: elementlar alohida tekshiriladi (qisman
 * muvaffaqiyat) */
export class BulkDto {
  @ArrayMaxSize(100, { message: "vazifalar — 1..100 element" })
  @ArrayMinSize(1, { message: "vazifalar — 1..100 element" })
  @IsArray({ message: "vazifalar — massiv" })
  vazifalar!: unknown[];
}
  • Tartib: @IsArray eng pastda — tur tekshiruvi birinchi.
  • BulkDto.vazifalar: unknown[] — @ValidateNested yo'q, ataylab. /toplam qisman muvaffaqiyat beradi (207): har element alohida tekshiriladi va o'z statusini oladi (Bulk va uzoq davom etuvchi operatsiyalar). Butun tana bitta xato bilan rad etilmasin — shuning uchun controller elementlarni o'zi, birma-bir tekshiradi.

9.2 Natija: 52/52

Variant to'liq yozilgach, 25–27-qismlarning 13 ta HTTP test fayli unga qarshi yurgizilgan (kanon o'lchovi, IlmHamroh, 2026-10, Vitest 5.0.5; ikki uzoq test nomi va vaqtlar tushirib qoldirildi):

text
 ✓ test/middleware.test.ts (3 tests) 265ms
 ✓ test/sana.test.ts (3 tests) 364ms
 ✓ test/xato.test.ts (3 tests) 407ms
 ✓ test/shartli.test.ts (3 tests) 421ms
 ✓ test/log.test.ts (4 tests) 416ms
 ✓ test/sxema.test.ts (4 tests) 481ms
 ✓ test/marshrutlar.test.ts (2 tests) 485ms
 ✓ test/toplam.test.ts (3 tests) 468ms
 ✓ test/api.test.ts (5 tests) 534ms
 ✓ test/rest.test.ts (7 tests) 550ms
 ✓ test/supertest.test.ts (5 tests) 573ms
 ✓ test/mock.test.ts (5 tests) 537ms
 ✓ test/idempotentlik.test.ts (5 tests) 1460ms

 Test Files  13 passed (13)
      Tests  52 passed (52)

9.3 33 ta noto'g'ri so'rov: 22 bir xil, 11 farq

Testlar o'tgani — "bir xil ishlaydi" degani emas. Kanon ikkala variantga 33 ta qo'lda tanlangan noto'g'ri so'rov yuborib, javoblarni solishtirgan: 22 tasi aynan bir xil, 11 tasida farq. Farqlarning ko'pi — xabar yoki joy matni. Ikkita ichma-ich holat:

text
FARQ     POST /v1/vazifalar [1,2]
   zod: …"detail":"Noto‘g‘ri kirish: kutilgan object, qabul qilingan massiv","kod":"validatsiya","xatolar":[{"joy":"body","xabar":"Noto‘g‘ri kirish: kutilgan object, qabul qilingan massiv"}],} 422
   dto: …"detail":"3 ta maydon noto'g'ri","kod":"validatsiya","xatolar":[{"joy":"body.0","xabar":"noma'lum parametr: 0"},{"joy":"body.1","xabar":"noma'lum parametr: 1"},{"joy":"body.matn","xabar":"matn — satr bo'lishi kerak"}],} 422
FARQ     PUT /v1/vazifalar {"vazifalar":[{"matn":"ok","bajarildi":false,"boshqa":1}]}
   zod: …"detail":"Noma’lum kalit: \"boshqa\"","kod":"validatsiya","xatolar":[{"joy":"body.vazifalar.0","xabar":"Noma’lum kalit: \"boshqa\""}],} 422
   dto: …"detail":"noma'lum parametr: boshqa","kod":"validatsiya","xatolar":[{"joy":"body.vazifalar.0.boshqa","xabar":"noma'lum parametr: boshqa"}],} 422

(Tananing boshi — type, title, status — ikkalasida bir xil, … bilan qisqartirdik.)

  • Tana massiv [1,2]. Zod: "obyekt kutilgan, massiv keldi" — bitta aniq xabar. class-validator massivni obyekt deb ko'rdi: 0 va 1 — "noma'lum parametr", matn — yo'q. Uchta xabar, hech biri asl sababni aytmaydi.
  • Ortiqcha maydon elementda. class-validator'ning joy i aniqroq (body.vazifalar.0.boshqa). Bu safar class-validator yutdi.

Qolgan ikkita haqiqiy xulq farqi (/01 → 200, /1e3 → 404) — o'tgan darsning «Loyiha qadami» da ko'rildi.

9.4 Nega kanonda Zod qoldi — qisqacha

To'liq o'lchovlar — Zod va Standard Schema NestJS'da darsida. Uch dalilning qisqasi:

  1. Tezlik. 6 parametrli ro'yxat so'rovida Zod variantining o'tkazuvchanligi class-validator variantidan ~2.2 marta yuqori (bitta mashina, navbat bilan o'lchangan).
  2. Bitta manba. Zod sxemasidan tur ham, tekshiruv ham, JSON Schema ham chiqadi. Frontend (15–22-qismlar) ham o'sha Zod'da. class-validator'da tur, qoida va aylantirish alohida yoziladi: query DTO 101 qator, Zod sxemasi 45. Obyekt darajasidagi qoidalar ("kamida bitta maydon", "offset va cursor birga emas") DTO'dan controller'ga ko'chdi.
  3. Bog'liqlik. kanon prod bog'liqligiga yangi paket qo'shilmaydi (Zod 25-qismdan bor); class-transformer'ning oxirgi versiyasi 2021-yilda chiqqan.

Hali commit qilmang. Validatsiya darslarining class-validator qismi shu bilan tugadi. Keyingi dars — pipe'lar va o'z pipe'imiz; u loyihaning nest tarmog'ida.

10. Real ishda

  • Nest loyihalarida update-x.dto.ts deyarli doim export class UpdateXDto extends PartialType(CreateXDto) {} — nest g resource shunday yaratadi. Review'da birinchi savol: "PATCH'da null nima bo'ladi?".
  • Ichma-ich DTO'lar — buyurtma (mahsulotlar ro'yxati), anketa (manzil, kontaktlar), import (ko'p yozuv). @Type unutilgani — eng ko'p uchraydigan validatsiya xatolaridan biri.
  • Swagger bilan ishlansa, mapped types @nestjs/swagger dan import qilinadi — aks holda API hujjatida maydonlar yo'qoladi (Swagger).
  • Kod review'da @ValidateNested() ko'rsangiz, qo'shni qatorda @Type(() => …) borligini tekshiring — bu ikkisi doim juft. Massiv bo'lsa, @ArrayMaxSize ham yonida bo'lsin.
  • Intervyu: "@ValidateNested nega @Type siz ishlamaydi?", "PartialType va TypeScript Partial farqi?".

Xulosa

  • Mapped types (PartialType, PickType, OmitType, IntersectionType) — klass yasaydi va qoidalarni ko'chiradi; TypeScript utility types faqat tur yasaydi.
  • PartialType — @IsOptional() qo'shadi: {"matn": null} va {} o'tadi; PATCH uchun @ValidateIf yoki qo'lda DTO.
  • Ichma-ich: @ValidateNested + @Type(() => X) — ikkalasi shart; @Type siz element tekshirilmaydi, whitelist uni bo'shatadi.
  • Massivda { each: true } va @ArrayMaxSize; guruhlar — groups, always, strictGroups.
  • Mapped types ichma-ich chaqiriladi (PartialType(OmitType(...))); Zod'dagi egizaklari — .partial(), .pick(), .omit(), .extend(), lekin .partial() null ni o'tkazmaydi.
  • class-validator varianti 52/52 testdan o'tdi; 33 so'rovdan 22 tasi bir xil, 11 tasida farq.

Keyingi dars: Pipe'lar — ParseIntPipe, ParseUUIDPipe, DefaultValuePipe, ParseArrayPipe va o'z PipeTransform imiz; vazifalar-api ning Standard Schema pipe'i shu yerda yoziladi.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Mapped types va nested validatsiya: PartialType, @ValidateNested, @Type va guruhlar — IlmHamroh