Coerce e Preprocess
z.coerce
z.coerce converte automaticamente il valore di input al tipo desiderato prima di validarlo. Usa i costruttori nativi JavaScript (String(), Number(), Boolean(), ecc.).
z.coerce.string()
Converte qualsiasi valore in stringa usando String():
import { z } from "zod";
const schema = z.coerce.string();
schema.parse(42); // => "42"
schema.parse(true); // => "true"
schema.parse(null); // => "null"
schema.parse(undefined); // => "undefined"
z.coerce.number()
Converte il valore usando Number():
const schema = z.coerce.number();
schema.parse("42"); // => 42
schema.parse("3.14"); // => 3.14
schema.parse(""); // => 0
schema.parse(true); // => 1
schema.parse(false); // => 0
schema.parse("abc"); // => NaN (attenzione!)
Per evitare NaN, combina con altre validazioni:
const sicuro = z.coerce.number().finite();
sicuro.parse("42"); // => 42
sicuro.parse("abc"); // Errore: NaN non e' finite
z.coerce.boolean()
Converte usando Boolean():
const schema = z.coerce.boolean();
schema.parse("true"); // => true
schema.parse("false"); // => true (attenzione! "false" e' truthy)
schema.parse(""); // => false
schema.parse(0); // => false
schema.parse(1); // => true
schema.parse(null); // => false
Attenzione: Boolean("false") restituisce true perche’ qualsiasi stringa non vuota e’ truthy. Per gestire i form HTML, usa z.preprocess().
z.coerce.date()
Converte usando new Date():
const schema = z.coerce.date();
schema.parse("2026-02-10"); // => Date
schema.parse(1707523200000); // => Date (da timestamp)
schema.parse("10 Febbraio 2026"); // => Date
schema.parse("non-una-data"); // Errore: Invalid Date
Puoi combinare con validazioni di data:
const dataNascita = z.coerce.date()
.min(new Date("1900-01-01"), "Data troppo indietro")
.max(new Date(), "La data non puo' essere nel futuro");
z.coerce.bigint()
const schema = z.coerce.bigint();
schema.parse("42"); // => 42n
schema.parse(42); // => 42n
z.preprocess()
z.preprocess() applica una funzione di trasformazione prima della validazione. Ti da’ controllo completo sulla conversione.
const stringToNumber = z.preprocess(
(val) => {
if (typeof val === "string") return parseInt(val, 10);
return val;
},
z.number().int().positive()
);
stringToNumber.parse("42"); // => 42
stringToNumber.parse(42); // => 42
stringToNumber.parse("abc"); // Errore (NaN non e' int/positive)
Gestire i Booleani dai Form HTML
Nei form HTML, i checkbox inviano "on" o non inviano nulla. Con z.preprocess() puoi gestirlo:
const checkboxSchema = z.preprocess(
(val) => {
if (typeof val === "string") {
return val === "true" || val === "on" || val === "1" || val === "si";
}
return Boolean(val);
},
z.boolean()
);
checkboxSchema.parse("on"); // => true
checkboxSchema.parse("true"); // => true
checkboxSchema.parse("false"); // => false
checkboxSchema.parse("1"); // => true
checkboxSchema.parse(undefined); // => false
Differenza tra coerce e preprocess
| Aspetto | z.coerce |
z.preprocess() |
|---|---|---|
| Controllo | Usa i costruttori JS nativi | Funzione custom |
| Flessibilita’ | Limitata | Totale |
| Semplicita’ | Molto semplice | Richiede piu’ codice |
| Gestione edge case | No | Si |
Usa z.coerce per conversioni semplici e standard. Usa z.preprocess() quando hai bisogno di logica personalizzata.
Caso d’Uso: Query Parameters
I query params di un URL sono sempre stringhe. Ecco come validarli:
const QueryParams = z.object({
pagina: z.coerce.number().int().positive().default(1),
limite: z.coerce.number().int().min(1).max(100).default(20),
ordine: z.enum(["asc", "desc"]).default("desc"),
cerca: z.string().optional(),
attivo: z.preprocess(
(val) => val === "true" || val === "1",
z.boolean().default(true)
),
});
// Simula i query params: ?pagina=2&limite=50&attivo=true
const params = QueryParams.parse({
pagina: "2",
limite: "50",
attivo: "true",
});
// => { pagina: 2, limite: 50, ordine: "desc", cerca: undefined, attivo: true }
Caso d’Uso: Form HTML
I form HTML inviano tutti i dati come stringhe. Ecco uno schema per gestirlo:
const FormContatto = z.object({
nome: z.string().min(1, "Nome obbligatorio").trim(),
email: z.string().email("Email non valida"),
eta: z.coerce.number({
invalid_type_error: "L'eta' deve essere un numero",
}).int().min(18, "Devi avere almeno 18 anni"),
dataNascita: z.coerce.date(),
budget: z.preprocess(
(val) => {
if (typeof val === "string") {
// Rimuovi simbolo valuta e separatore migliaia
return parseFloat(val.replace(/[€\s.]/g, "").replace(",", "."));
}
return val;
},
z.number().positive("Il budget deve essere positivo")
),
accettaTermini: z.preprocess(
(val) => val === "on" || val === "true" || val === true,
z.literal(true, {
errorMap: () => ({ message: "Devi accettare i termini" }),
})
),
});
// Dati dal form HTML
FormContatto.parse({
nome: " Marco Rossi ",
email: "marco@esempio.it",
eta: "28",
dataNascita: "1998-05-15",
budget: "1.500,00",
accettaTermini: "on",
});
Caso d’Uso: Environment Variables
Le variabili d’ambiente sono sempre stringhe:
const EnvSchema = z.object({
NODE_ENV: z.enum(["development", "production", "test"]),
PORT: z.coerce.number().int().default(3000),
DATABASE_URL: z.string().url(),
JWT_SECRET: z.string().min(32),
ENABLE_CACHE: z.preprocess(
(val) => val === "true" || val === "1",
z.boolean().default(false)
),
MAX_UPLOAD_SIZE: z.coerce.number().positive().default(10485760),
});
const env = EnvSchema.parse(process.env);