Mundarija (33)
- Bu darsda
- 1. Nega bu kerak?
- 2. Mapped types
- 2.1 To'rt funksiya
- 2.2 Ichma-ich chaqirish
- 2.3 Zod'da xuddi shu g'oya
- 2.4 Qanday ishlaydi
- 2.5 Haqiqiy natijalar
- 3. PartialType ning ikki tuzog'i
- 3.1 null o'tadi
- 3.2 Bo'sh {} o'tadi
- 3.3 Yechim
- 4. Ichma-ich DTO'lar
- 4.1 @ValidateNested va @Type
- 4.2 @Type siz — tekshiruv yo'q
- 4.3 Massiv elementlari: each
- 5. Validatsiya guruhlari
- 5.1 Bitta DTO — ikki qoida to'plami
- 5.2 Guruhsiz chaqiruv tuzog'i
- 6. Hujumchi nigohi
- 7. Ko'p uchraydigan xatolar
- 8. Mashqlar
- 1-mashq (oson): qaysi funksiya
- 2-mashq (o'rta): PartialType modeli
- 3-mashq (qiyin): ichma-ich tekshiruv
- 9. Loyiha qadami: variantning ichma-ich DTO'lari va yakuniy natija (loyihaga kirmaydi)
- 9.1 ToplamDto va BulkDto
- 9.2 Natija: 52/52
- 9.3 33 ta noto'g'ri so'rov: 22 bir xil, 11 farq
- 9.4 Nega kanonda Zod qoldi — qisqacha
- 10. Real ishda
- Xulosa
- Manbalar
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).@Typeni unutsangiz, ichki element umuman tekshirilmaydi — xatosiz. Bitta DTO'ni turli amallarda turlicha tekshirish — validatsiya guruhlari (groups).
Bu darsda
PartialType,PickType,OmitType,IntersectionTypebilan DTO'larni takrorsiz yasaysiz.PartialTypening ikki tuzog'ini (nullva bo'sh{}) haqiqiy javobda ko'rasiz.- Ichma-ich obyektlar va massivlarni
@ValidateNested+@Typebilan tekshirasiz va@Typesiz 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.
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:
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):
{} → []
{"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'daYangiVazifa.pick({ matn: true }). Ikkalasiga{"matn": "Non", "bajarildi": true}berildi (class-validator'daforbidNonWhitelistedyoqilgan, 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:
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:
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:
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
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»):
nulluchun —@ValidateIf((o) => o.matn !== undefined)@IsOptional()o'rniga: "maydon berilgan bo'lsa —nullbo'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,forbidNonWhitelistedyoqilgan. 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:
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 obyektlarniElementnusxasiga 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:
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". Mijozmatnni 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:
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:
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()eachsiz).teglaruzunligi — "teglar dagi har qiymat 1–10 belgi bo'lishi kerak". E'tibor bering: qaysi element (""yoki"juda-uzun-teg-nomi") aytilmagan —eachxabari butun massiv uchun bitta.teglarsoni — 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:
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:
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'] }) idbor DTO globalValidationPipe()(guruhsiz) bilan POST'da ishlatildi. Mijozidyubormadi. 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
@Typesiz ichma-ich maydon. Massiv elementi tekshirilmaydi —{"vazifalar":[{"matn":{"$gt":""},"egasi_id":1}]}handler'ga yetadi. Himoya —@ValidateNested+@Typejuftligi va har element uchunforbidNonWhitelisted.- Ulkan massiv. Elementlar soni cheklanmasa, tekshiruvning o'zi CPU'ni band qiladi. Himoya —
@ArrayMaxSize(vazifalar-apida — 1000 va 100). - Chuqur ichma-ichlik. Har daraja — yana
plainToInstancevavalidate. Himoya — tana hajmi chegarasi (express.json({ limit: "16kb" })) va DTO tuzilmasini cheklash; rekursiv DTO'dan qoching. - PATCH bilan majburiy maydonni o'chirish.
PartialType+{"matn": null}— tekshiruvdan o'tadi. Himoya —@ValidateIf(o => o.matn !== undefined)yoki ombor darajasidagiNOT NULLva 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
YangiDtoning hamma maydoni ixtiyoriy —(YangiDto)- Faqat
emailvaparol—(FoydalanuvchiDto, ["email", "parol"] as const) parolHashdan boshqa hammasi —(Foydalanuvchi, ["parolHash"] as const)- Ichki elementlarni klassga aylantiradigan dekorator —
@(() => Element) - Ichki elementga kirib tekshiradigan dekorator —
@()
Yechim
- PartialType — har maydonga
@IsOptional()qo'shadi (vanullni 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
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:
[ 'matn: satr', "matn: bo'sh emas" ]
[]
[]
[ "matn: bo'sh emas" ]
2 3Uchinchi 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
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:
[ '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.
// 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:
@IsArrayeng pastda — tur tekshiruvi birinchi. BulkDto.vazifalar: unknown[]—@ValidateNestedyo'q, ataylab./toplamqisman 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):
✓ 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:
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:0va1— "noma'lum parametr",matn— yo'q. Uchta xabar, hech biri asl sababni aytmaydi. - Ortiqcha maydon elementda. class-validator'ning
joyi 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:
- Tezlik. 6 parametrli ro'yxat so'rovida Zod variantining o'tkazuvchanligi class-validator variantidan ~2.2 marta yuqori (bitta mashina, navbat bilan o'lchangan).
- 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.
- 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.tsdeyarli doimexport class UpdateXDto extends PartialType(CreateXDto) {}—nest g resourceshunday yaratadi. Review'da birinchi savol: "PATCH'danullnima bo'ladi?". - Ichma-ich DTO'lar — buyurtma (mahsulotlar ro'yxati), anketa (manzil, kontaktlar), import (ko'p yozuv).
@Typeunutilgani — eng ko'p uchraydigan validatsiya xatolaridan biri. - Swagger bilan ishlansa, mapped types
@nestjs/swaggerdan 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,@ArrayMaxSizeham yonida bo'lsin. - Intervyu: "
@ValidateNestednega@Typesiz ishlamaydi?", "PartialTypeva TypeScriptPartialfarqi?".
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@ValidateIfyoki qo'lda DTO.- Ichma-ich:
@ValidateNested+@Type(() => X)— ikkalasi shart;@Typesiz element tekshirilmaydi,whitelistuni 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()nullni 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
- NestJS hujjati — Mapped types: https://docs.nestjs.com/openapi/mapped-types va Validation: https://docs.nestjs.com/techniques/validation
- class-validator — nested validation,
each, validation groups: https://github.com/typestack/class-validator - class-transformer —
@Type: https://github.com/typestack/class-transformer - RFC 7396 — JSON Merge Patch (
null— o'chirish): https://www.rfc-editor.org/rfc/rfc7396 - Kanon (IlmHamroh, 2026-10-27): mapped types va nested tajribasi (NestJS 12.2.0, Vitest ichida), class-validator varianti — 52/52 test, 33 so'rovli solishtiruv. Demo (
validate()bilan guruhlar, nested obyekt,PartialType/PickType, ichma-ichPartialType(OmitType(...))) — biz ishga tushirdik (2026-11-02,@nestjs/mapped-types12.0.0, class-validator 0.15.1, Node 24.21.0).
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!