Union, Enum e Literal
z.literal()
Valida un valore esatto e immutabile:
import { z } from "zod";
const cinque = z.literal(5);
cinque.parse(5); // OK
cinque.parse(6); // Errore
const ciao = z.literal("ciao");
ciao.parse("ciao"); // OK
ciao.parse("mondo"); // Errore
const vero = z.literal(true);
vero.parse(true); // OK
vero.parse(false); // Errore
z.enum()
Crea un enum Zod da un array di stringhe. Il tipo risultante e’ una union di stringhe letterali.
const Ruolo = z.enum(["admin", "utente", "moderatore"]);
Ruolo.parse("admin"); // OK
Ruolo.parse("ospite"); // Errore
type Ruolo = z.infer<typeof Ruolo>;
// "admin" | "utente" | "moderatore"
Accedere ai Valori
Ruolo.options; // ["admin", "utente", "moderatore"]
Ruolo.enum; // { admin: "admin", utente: "utente", moderatore: "moderatore" }
// Utile per accedere in modo type-safe
Ruolo.enum.admin; // "admin"
Escludere o Estrarre Valori
const SoloAdmin = Ruolo.extract(["admin", "moderatore"]);
type SoloAdmin = z.infer<typeof SoloAdmin>;
// "admin" | "moderatore"
const SenzaAdmin = Ruolo.exclude(["admin"]);
type SenzaAdmin = z.infer<typeof SenzaAdmin>;
// "utente" | "moderatore"
z.nativeEnum()
Se hai gia’ un enum TypeScript, puoi usarlo direttamente:
enum Colore {
Rosso = "rosso",
Verde = "verde",
Blu = "blu",
}
const ColoreSchema = z.nativeEnum(Colore);
ColoreSchema.parse(Colore.Rosso); // OK
ColoreSchema.parse("rosso"); // OK
ColoreSchema.parse("giallo"); // Errore
type ColoreType = z.infer<typeof ColoreSchema>;
// Colore
Funziona anche con enum numerici:
enum Stato {
Attivo = 0,
Inattivo = 1,
Sospeso = 2,
}
const StatoSchema = z.nativeEnum(Stato);
StatoSchema.parse(0); // OK
StatoSchema.parse(3); // Errore
z.union()
Crea un tipo unione: il dato deve corrispondere ad almeno uno degli schemi.
const StringaONumero = z.union([z.string(), z.number()]);
StringaONumero.parse("ciao"); // OK
StringaONumero.parse(42); // OK
StringaONumero.parse(true); // Errore
Union di Oggetti
const Risultato = z.union([
z.object({ success: z.literal(true), data: z.string() }),
z.object({ success: z.literal(false), errore: z.string() }),
]);
Risultato.parse({ success: true, data: "ok" }); // OK
Risultato.parse({ success: false, errore: "fallito" }); // OK
z.discriminatedUnion()
Piu’ efficiente di z.union() quando gli oggetti hanno un campo discriminante comune:
const Evento = z.discriminatedUnion("tipo", [
z.object({
tipo: z.literal("click"),
x: z.number(),
y: z.number(),
}),
z.object({
tipo: z.literal("keypress"),
tasto: z.string(),
}),
z.object({
tipo: z.literal("scroll"),
direzione: z.enum(["su", "giu"]),
distanza: z.number(),
}),
]);
Evento.parse({ tipo: "click", x: 100, y: 200 }); // OK
Evento.parse({ tipo: "keypress", tasto: "Enter" }); // OK
Evento.parse({ tipo: "scroll", direzione: "su", distanza: 50 }); // OK
type Evento = z.infer<typeof Evento>;
Il vantaggio e’ che Zod guarda prima il campo tipo e poi valida solo lo schema corrispondente, generando messaggi di errore piu’ precisi.
z.intersection()
Combina due schemi: il dato deve soddisfare entrambi:
const ConNome = z.object({ nome: z.string() });
const ConEta = z.object({ eta: z.number() });
const Persona = z.intersection(ConNome, ConEta);
Persona.parse({ nome: "Marco", eta: 28 }); // OK
Persona.parse({ nome: "Marco" }); // Errore: manca eta
type Persona = z.infer<typeof Persona>;
// { nome: string } & { eta: number }
Nella maggior parte dei casi, .merge() e’ preferibile a z.intersection() per gli oggetti.
Optional e Nullable
.optional()
Rende il campo opzionale (undefined e’ accettato):
const schema = z.object({
nome: z.string(),
bio: z.string().optional(), // string | undefined
});
schema.parse({ nome: "Marco" }); // OK
schema.parse({ nome: "Marco", bio: undefined }); // OK
schema.parse({ nome: "Marco", bio: "Dev" }); // OK
.nullable()
Accetta anche null:
const campo = z.string().nullable();
campo.parse("ciao"); // OK
campo.parse(null); // OK
campo.parse(undefined); // Errore
.nullish()
Combina optional e nullable:
const campo = z.string().nullish();
campo.parse("ciao"); // OK
campo.parse(null); // OK
campo.parse(undefined); // OK
type Campo = z.infer<typeof campo>;
// string | null | undefined
.unwrap()
Per recuperare lo schema interno da un optional o nullable:
const opzionale = z.string().optional();
const interno = opzionale.unwrap();
// => z.string()
Esempio Pratico: Sistema di Notifiche
const Notifica = z.discriminatedUnion("canale", [
z.object({
canale: z.literal("email"),
destinatario: z.string().email(),
oggetto: z.string(),
corpo: z.string(),
}),
z.object({
canale: z.literal("sms"),
numero: z.string().regex(/^\+\d{10,15}$/),
messaggio: z.string().max(160),
}),
z.object({
canale: z.literal("push"),
deviceToken: z.string(),
titolo: z.string(),
corpo: z.string().optional(),
}),
]);
type Notifica = z.infer<typeof Notifica>;