Zod — zaawansowane wzorce
Discriminated unions, branded types (nominal typing), transforms, refine, recursive schemas (z.lazy), React Hook Form, tRPC i env validation.
6 zaawansowanych wzorców Zod
discriminatedUnion, brand, transform, refine, lazy i pipe — kategoria i zastosowanie w produkcyjnych aplikacjach TypeScript.
| Feature | Kategoria | Zastosowanie |
|---|---|---|
| z.discriminatedUnion() | Typy | Union z discriminant key — lepsza wydajność niż z.union() |
| .brand('UserId') | Branded types | Nominal typing — UserId nie jest string (TypeScript-only) |
| .transform(fn) | Transformacja | Konwersja wartości — string to Date, trim, lowercase |
| .refine(fn, msg) | Custom validation | Własna walidacja — async, cross-field (superRefine) |
| z.lazy(() => schema) | Recursive | Cykliczne struktury — JSON, drzewo kategorii |
| .pipe(schema) | Kompozycja | Chain schematów — coerce + validate w jednym |
Często zadawane pytania
Co to jest Zod i dlaczego jest standardem walidacji w TypeScript?
Zod: TypeScript-first schema validation. Runtime walidacja z pełnym type inference. Brak powtórzenia typów — jeden schema = typy + walidacja. Instalacja: npm install zod. Podstawy: import {z} from 'zod'. const UserSchema = z.object({name: z.string(), age: z.number().min(0).max(150), email: z.string().email(), role: z.enum(['admin', 'user', 'guest'])}). type User = z.infer(UserSchema). const result = UserSchema.safeParse(data). result.success: result.data lub result.error. Zalety nad ręcznymi typami: Walidacja runtime (np. dane z API, formularze). Type inference — nie piszesz typów ręcznie. Kompozycyjność schematów. Czytelne komunikaty błędów. Integracje: react-hook-form + zodResolver. Vercel AI SDK (generateObject). tRPC (input validation). Prisma (query params). Drizzle (insert schema). Zod vs Yup: Zod — TypeScript-first, lepszy type inference. Yup — starsza, mniej TypeScript-friendly. Zod vs io-ts: Zod — prostszy API. io-ts — czysto funkcyjny (fp-ts). Zod vs Valibot: Valibot — mniejszy bundle (3KB vs 13KB). Zod — bardziej popularny, więcej integracji. Parse nie validate: Zod.parse() rzuca przy błędzie. safeParse() bezpieczna wersja. parseAsync() dla async refine.
Zaawansowane typy Zod — discriminated unions, branded types i transforms?
Discriminated Union: const ShapeSchema = z.discriminatedUnion('type', [z.object({type: z.literal('circle'), radius: z.number()}), z.object({type: z.literal('rect'), width: z.number(), height: z.number()})]). Lepsza wydajność niż z.union() — sprawdza discriminant key. Branded Types (Nominal typing): const UserId = z.string().uuid().brand('UserId'). type UserId = z.infer(typeof UserId). function getUser(id: UserId) {...}. getUser('not-branded-string') — TypeScript błąd! Różne ID types nie są wymienne. Brand nie wpływa na runtime — tylko TypeScript. Transforms: z.string().transform(s => s.trim().toLowerCase()). z.string().transform(s => parseInt(s, 10)). z.preprocess(val => String(val), z.string()). TransformSchema.._input vs ._output — różne typy wejścia i wyjścia. Coerce: z.coerce.number() — konwertuje string do number. z.coerce.date() — konwertuje string do Date. Przydatne dla form data (wszystko string). Intersection: const A = z.object({a: z.string()}). const B = z.object({b: z.number()}). const AB = z.intersection(A, B). lub A.merge(B) — łącz obiekty. Partial i Required: UserSchema.partial() — wszystkie pola opcjonalne. UserSchema.required() — wszystkie wymagane. UserSchema.partial({name: true}) — tylko name opcjonalne. Pick i Omit: UserSchema.pick({name: true, email: true}). UserSchema.omit({password: true}).
Refinements i custom validation w Zod?
Refine — custom walidacja: z.string().refine(s => s.startsWith('https'), {message: 'Must start with https'}). z.number().refine(n => n % 2 === 0, 'Must be even'). Async refine: z.string().refine(async email => await checkEmailExists(email), 'Email already taken'). parseAsync() dla async refine. superRefine — wielokrotne błędy: z.object({password: z.string(), confirm: z.string()}).superRefine(({password, confirm}, ctx) => { if (password !== confirm) { ctx.addIssue({code: z.ZodIssueCode.custom, message: 'Passwords must match', path: ['confirm']}) } }). Walidacja warunkowa: z.discriminatedUnion lub z.union dla różnych kształtów danych. businessHours.superRefine oparty na dayOfWeek. Wzorzec: schema z refine per field. Custom error messages: z.string().min(8, {message: 'Hasło musi mieć min. 8 znaków'}). z.string().email('Nieprawidłowy email'). ZodError format: error.errors — tablica ZodIssue. issue.path — ścieżka do pola. issue.message — komunikat. issue.code — typ błędu. flatten(): const flat = error.flatten(). flat.fieldErrors — per field. flat.formErrors — globalne błędy. format(): const formatted = error.format(). nested errors. Custom ZodErrorMap: z.setErrorMap(customErrorMap). Globalne custom messages. Internacjonalizacja (i18n) błędów. Pipe: z.string().pipe(z.coerce.number()) — transform z walidacją.
Zod z React Hook Form, tRPC i API validation?
React Hook Form + Zod: npm install react-hook-form @hookform/resolvers. const schema = z.object({email: z.string().email(), password: z.string().min(8)}). const {register, handleSubmit, formState} = useForm({resolver: zodResolver(schema)}). formState.errors.email.message. Automatyczne type safety w form submit. TypeScript: SubmitHandler(z.infer(typeof schema)). tRPC + Zod input: publicProcedure.input(z.object({userId: z.string().uuid()})).query(async ({input}) => { return db.user.findUnique({where: {id: input.userId}}) }). Walidacja na poziomie procedury. Błędy walidacji jako TRPCError. API Route validation (Next.js): export async function POST(req: Request) { const body = await req.json(). const parsed = CreateUserSchema.safeParse(body). if (!parsed.success) { return Response.json({errors: parsed.error.flatten()}, {status: 400}) } const user = await createUser(parsed.data) }. Prisma + Zod: import {createInsertSchema, createSelectSchema} from 'drizzle-zod'. lub prisma-zod-generator. Automatyczne schema z modelu DB. Environment variables: import {createEnv} from '@t3-oss/env-nextjs'. env({server: {DATABASE_URL: z.string().url()}, client: {NEXT_PUBLIC_API_URL: z.string().url()}}). Walidacja env przy starcie. Nie zapomnisz o zmiennej env. URL search params: const paramsSchema = z.object({page: z.coerce.number().default(1), q: z.string().optional()}). paramsSchema.parse(Object.fromEntries(searchParams)).
Zaawansowane wzorce Zod — lazy, recursive i schema composition?
Recursive schemas (JSON): type Json = string | number | boolean | null | Json[] | {[key: string]: Json}. const JsonSchema: z.ZodType(Json) = z.lazy(() => z.union([z.string(), z.number(), z.boolean(), z.null(), z.array(JsonSchema), z.record(JsonSchema)])). lazy() dla cyklicznych referencji. Tree/nested struktura: const CategorySchema = z.object({id: z.number(), name: z.string(), children: z.array(z.lazy(() => CategorySchema)).optional()}). Record schema: z.record(z.string(), z.number()) — {[key: string]: number}. z.record(z.enum(['a', 'b']), z.string()) — tylko konkretne klucze. Map i Set: z.map(z.string(), z.number()). z.set(z.string()). Tuple: z.tuple([z.string(), z.number(), z.boolean()]) — fixed-length array. rest() dla dodatkowych elementów. Schema extension: const BaseSchema = z.object({id: z.string(), createdAt: z.date()}). const UserSchema = BaseSchema.extend({name: z.string(), email: z.string().email()}). extend() — nie mutuje oryginalnego. Schema registry pattern: Map(schemaKey => ZodSchema). Dynamiczna walidacja per type. Serializable schemas. ZodType utility: z.ZodType(T) — dla generic schemas. Infer nested: type UserWithPosts = z.infer(typeof UserWithPostsSchema). Default values: z.string().default('anonymous'). z.number().default(0). z.object().default({}) — parse pusty object. Catch: z.number().catch(0) — default przy błędzie zamiast throw. Optional vs Nullable vs default: optional() — undefined ok. nullable() — null ok. default() — zastąp undefined. nullish() — null i undefined ok.
Powiązane artykuły
Skontaktuj się z nami
Porozmawiajmy o Twoim projekcie. Bezpłatna wycena w ciągu 24 godzin.
Wyślij zapytanie
Telefon
+48 790 814 814
Pon-Pt: 9:00 - 18:00
adam@fotz.pl
Odpowiadamy w ciągu 24h
Adres
Plac Wolności 16
61-739 Poznań
Godziny pracy
Wolisz porozmawiać?
Zadzwoń teraz i porozmawiaj z naszym specjalistą o Twoim projekcie.
Zadzwoń teraz