Oggetti e Nested
z.object() in Dettaglio
z.object() è il cuore di Zod. Definisce la struttura di un oggetto con chiavi e tipi specifici.
import { z } from "zod";
const Utente = z.object({
id: z.number(),
nome: z.string(),
email: z.string().email(),
eta: z.number().optional(),
});
type Utente = z.infer<typeof Utente>;
// { id: number; nome: string; email: string; eta?: number }
Per default, Zod ignora (strip) le proprietà extra non dichiarate nello schema.
Oggetti Nested
Puoi annidare z.object() all’interno di altri schemi:
const Indirizzo = z.object({
via: z.string(),
citta: z.string(),
cap: z.string().length(5),
provincia: z.string().length(2).toUpperCase(),
});
const UtenteCompleto = z.object({
nome: z.string(),
email: z.string().email(),
indirizzo: Indirizzo,
contatti: z.object({
telefono: z.string().optional(),
cellulare: z.string(),
}),
});
type UtenteCompleto = z.infer<typeof UtenteCompleto>;
.pick() e .omit()
Come i tipi utility di TypeScript Pick e Omit:
const Utente = z.object({
id: z.number(),
nome: z.string(),
email: z.string().email(),
password: z.string(),
});
// Solo nome ed email
const UtentePublico = Utente.pick({ nome: true, email: true });
type UtentePublico = z.infer<typeof UtentePublico>;
// { nome: string; email: string }
// Tutto tranne password
const UtenteSenzaPassword = Utente.omit({ password: true });
type UtenteSenzaPassword = z.infer<typeof UtenteSenzaPassword>;
// { id: number; nome: string; email: string }
.partial() e .required()
.partial() rende tutti i campi opzionali (come Partial<T> in TypeScript):
const Utente = z.object({
nome: z.string(),
email: z.string(),
eta: z.number(),
});
const UtentePartial = Utente.partial();
type UtentePartial = z.infer<typeof UtentePartial>;
// { nome?: string; email?: string; eta?: number }
Puoi rendere opzionali solo campi specifici:
const AggiornamentoUtente = Utente.partial({ email: true, eta: true });
type AggiornamentoUtente = z.infer<typeof AggiornamentoUtente>;
// { nome: string; email?: string; eta?: number }
.required() fa l’opposto, rendendo tutti i campi obbligatori:
const UtenteRequired = UtentePartial.required();
// Tutti i campi tornano obbligatori
.extend() e .merge()
.extend()
Aggiunge nuovi campi a uno schema esistente:
const Base = z.object({
nome: z.string(),
email: z.string(),
});
const ConRuolo = Base.extend({
ruolo: z.enum(["admin", "utente"]),
createdAt: z.date(),
});
type ConRuolo = z.infer<typeof ConRuolo>;
// { nome: string; email: string; ruolo: "admin" | "utente"; createdAt: Date }
.extend() può anche sovrascrivere campi esistenti:
const Sovrascritto = Base.extend({
email: z.string().email().optional(), // ora è opzionale
});
.merge()
Unisce due schemi oggetto. Simile a .extend() ma accetta un intero schema:
const InfoBase = z.object({ nome: z.string(), eta: z.number() });
const InfoContatto = z.object({ email: z.string(), telefono: z.string() });
const InfoCompleta = InfoBase.merge(InfoContatto);
type InfoCompleta = z.infer<typeof InfoCompleta>;
// { nome: string; eta: number; email: string; telefono: string }
.passthrough(), .strict() e .strip()
Questi metodi controllano come Zod gestisce le proprietà non riconosciute.
.strip() (Default)
Rimuove le proprietà extra:
const schema = z.object({ nome: z.string() });
schema.parse({ nome: "Marco", extra: true });
// => { nome: "Marco" } -- "extra" viene rimossa
.passthrough()
Mantiene le proprietà extra nell’output:
const schema = z.object({ nome: z.string() }).passthrough();
schema.parse({ nome: "Marco", extra: true });
// => { nome: "Marco", extra: true }
.strict()
Lancia un errore se ci sono proprietà extra:
const schema = z.object({ nome: z.string() }).strict();
schema.parse({ nome: "Marco" }); // OK
schema.parse({ nome: "Marco", extra: true }); // Errore!
// ZodError: Unrecognized key(s) in object: 'extra'
.keyof()
Estrae le chiavi di uno schema come enum:
const Utente = z.object({
nome: z.string(),
email: z.string(),
eta: z.number(),
});
const ChiaviUtente = Utente.keyof();
ChiaviUtente.parse("nome"); // OK
ChiaviUtente.parse("email"); // OK
ChiaviUtente.parse("altro"); // Errore
type Key = z.infer<typeof ChiaviUtente>;
// "nome" | "email" | "eta"
Esempio Pratico: CRUD
// Schema base
const Prodotto = z.object({
id: z.number().int().positive(),
nome: z.string().min(1),
prezzo: z.number().positive(),
categoria: z.string(),
attivo: z.boolean().default(true),
});
// Per la creazione (senza id, viene generato dal DB)
const CreaProdotto = Prodotto.omit({ id: true });
// Per l'aggiornamento (tutti i campi opzionali tranne id)
const AggiornaProdotto = Prodotto.partial().required({ id: true });
// Per la risposta API (con timestamp aggiuntivi)
const ProdottoResponse = Prodotto.extend({
createdAt: z.string().datetime(),
updatedAt: z.string().datetime(),
});
type CreaProdotto = z.infer<typeof CreaProdotto>;
type AggiornaProdotto = z.infer<typeof AggiornaProdotto>;
type ProdottoResponse = z.infer<typeof ProdottoResponse>;