Vol. 7 · № 02 · Un atelier TypeScriptA TypeScript workshop

Parse, don't validate, ou transformer l'inconnu en prouvé Parse, don't validate, or turning unknown into proven

Valider — vérifier un booléen puis caster en as — rouvre le trou : le check et le type dérivent, le as est un pari. Parser fait l'inverse, en un geste : une fonction prend de l'unknown et rend une valeur typée prouvée, ou échoue. Le schéma est la source unique — il valide, et z.infer en dérive le type. On parse une fois, au bord ; le cœur fait confiance. Le réflexe le plus quotidien de la série. Validating — checking a boolean then casting with as — reopens the hole: check and type drift, the as is a bet. Parsing does the opposite, in one move: a function takes unknown and returns a proven typed value, or fails. The schema is the single source — it validates, and z.infer derives the type. Parse once, at the edge; the core trusts. The series' most everyday reflex.

AudienceAudience
Dev qui a vu le mensonge de la frontière et veut la franchir sans rouvrir le trou Dev who saw the boundary's lie and wants to cross it without reopening the hole
Format
Self-paced
ChapitresChapters
5
Date
Juin 2026 Jun 2026
≈ 18 min ●●○○ ParsingZodTradeoff
SommaireContents ·
№ 01CadrageFraming3 min

Valider puis caster rouvre le trou. Parser le ferme en un seul geste.Validate then cast reopens the hole. Parsing shuts it in one move.

Le numéro précédent a posé le diagnostic : au bord, la donnée est unknown. Reste à la rendre typée honnêtement. Le réflexe courant — valider — vérifie un booléen, puis caste en as vers le type attendu. Deux gestes, deux trous : le check et le type dérivent (le guard menteur du 2.3), et le as est une promesse non vérifiée (4.3). Parser fait l'inverse : une fonction qui prend unknown et rend une valeur typée prouvée, ou échoue. La donnée n'est jamais castée — elle est transformée.The previous issue gave the diagnosis: at the edge, data is unknown. What remains is to make it honestly typed. The common reflex — validate — checks a boolean, then casts with as to the expected type. Two moves, two holes: the check and the type drift (the lying guard of 2.3), and the as is an unchecked promise (4.3). Parsing does the opposite: a function that takes unknown and returns a proven typed value, or fails. The data is never cast — it's transformed.

Valider puis caster — l'écart que le as comble en mentantValidate then cast — the gap the as fills by lying
function isUser(x: unknown): boolean {        // valide : rend un boolean
  return typeof x === "object" && x !== null && "name" in x;
}

if (isUser(data)) {
  const user = data as User;   // ✗ on a vérifié 'name', on caste vers User ENTIER
  user.email.trim();           // 💥 email jamais vérifié — le 'as' a comblé l'écart
}
Que produit la frontière ?What does the boundary produce?
Valider + asValidate + as
un booléen, puis un paria boolean, then a bet
check et type peuvent divergercheck and type can drift
Parser (schéma)Parse (schema)
une valeur typée prouvéea proven typed value
ou un échec, ici, au bordor a failure, here, at the edge

Valider rend un boolean — et l'on caste ensuite, sur la foi de ce booléen, vers un type qu'il n'a pas entièrement vérifié. Parser retourne le type : si la donnée passe, on tient un User réel ; sinon, ça lève sur place.Validating returns a boolean — and you then cast, on the strength of that boolean, to a type it didn't fully check. Parsing returns the type: if the data passes, you hold a real User; otherwise, it throws on the spot.

Parser — unknown entre, un type prouvé sortParse — unknown in, a proven type out
const User = z.object({ name: z.string(), email: z.string() });

const user = User.parse(data);   // ✓ valide ET produit un User — ou lève ICI, au bord
user.email.trim();               // user : z.infer<typeof User> — prouvé, zéro 'as'
Le réflexe du numéroThe issue's reflex

« À la frontière, je parse — je transforme une donnée inconnue en une donnée typée prouvée — je ne valide pas. » Vérifier puis caster en as, c'est rouvrir le trou ; parser le ferme, parce que le type sort de la vérification elle-même."At the boundary, I parse — I turn unknown data into proven typed data — I don't validate." Checking then casting with as reopens the hole; parsing shuts it, because the type comes out of the verification itself.

Le cœur pratique de la série.The series' practical heart.

Tout converge ici. Le type ne vérifie pas la donnée (Vol 2) ; any et as ne font que masquer (Vol 4) ; la frontière ment (7.1). « Parse, don't validate » est la discipline qui répond à tout cela en un geste — et c'est le réflexe que le développeur emportera le plus souvent dans son travail quotidien.Everything converges here. The type doesn't check the data (Vol 2); any and as only mask (Vol 4); the boundary lies (7.1). 'Parse, don't validate' is the discipline that answers all of it in one move — and it's the reflex the developer will carry most often into daily work.

№ 02La surpriseThe surprise4 min

Le schéma est le type. On l'écrit une fois ; z.infer en déduit le reste.The schema is the type. You write it once; z.infer deduces the rest.

La surprise pour qui découvre les schémas : on n'écrit pas le type d'un côté et le validateur de l'autre. On écrit le schéma, et le type en est dérivé par z.infer. Une seule source de vérité. Ajoute un champ au schéma, et le type statique comme le contrôle à l'exécution se mettent à jour ensemble — impossible de les désynchroniser. C'est le réflexe « calculer, pas recopier » du Vol 6, appliqué à l'endroit où il compte le plus : la frontière.The surprise for newcomers to schemas: you don't write the type on one side and the validator on the other. You write the schema, and the type is derived from it via z.infer. One source of truth. Add a field to the schema, and both the static type and the runtime check update together — impossible to desync. It's Vol 6's 'compute, don't copy' reflex, applied where it matters most: the boundary.

L'interface et le validateur, écrits séparémentThe interface and validator, written separately
interface User { name: string; email: string; age: number }   // ← 1re vérité

function isUser(x: unknown): x is User {                       // ← 2e vérité, à la main
  return typeof x === "object" && x !== null
    && "name" in x && "email" in x;     // 'age' oublié — le guard ment (2.3), et dérive
}

Le type dit trois champs, le validateur n'en vérifie que deux : deux descriptions de la même chose, que rien n'oblige à concorder. C'est exactement le guard menteur du 2.3 et la dérive du 6.1 — rejouée à l'endroit le plus dangereux, l'entrée des données.The type says three fields, the validator checks only two: two descriptions of the same thing, nothing forcing them to agree. It's exactly the lying guard of 2.3 and the drift of 6.1 — replayed at the most dangerous spot, the data entry.

Écrire le type et son validateur, c'est entretenir deux vérités. Le schéma n'en est qu'une : il valide, et le type en découle — jamais l'inverse.Writing the type and its validator means maintaining two truths. The schema is only one: it validates, and the type follows — never the reverse.
№ 03La mécaniqueThe mechanics5 min

Un objet runtime qui sait vérifier — et dont z.infer tire le type.A runtime object that knows how to check — and from which z.infer draws the type.

Le schéma est une valeur, vivante à l'exécution, qui sait contrôler une donnée. .parse(x) lève si elle ne colle pas ; .safeParse(x) rend plutôt une union discriminée { success: true, data } | { success: false, error } — exactement le Vol 3, qu'on narrowe sur .success. Et z.infer<typeof S> extrait le type statique. Les schémas se composent : objets, tableaux, optionnels, enums. Ce qui en sort est un DTO : la donnée typée et prouvée à laquelle tout le reste du programme peut enfin se fier.The schema is a value, alive at runtime, that knows how to check data. .parse(x) throws if it doesn't fit; .safeParse(x) instead returns a discriminated union { success: true, data } | { success: false, error } — exactly Vol 3, narrowed on .success. And z.infer<typeof S> extracts the static type. Schemas compose: objects, arrays, optionals, enums. What comes out is a DTO: the typed, proven data the rest of the program can finally trust.

safeParse — l'échec devient une union discriminée (Vol 3)safeParse — failure becomes a discriminated union (Vol 3)
const result = User.safeParse(data);   // ne lève pas — rend une union discriminée

if (result.success) {
  result.data.email;       // ✓ result.data : User — prouvé (Vol 3 : on narrowe sur .success)
} else {
  log(result.error);       // l'erreur structurée, à traiter au bord
}
Les schémas se composent — objets, tableaux, enumsSchemas compose — objects, arrays, enums
const Order = z.object({
  id: z.string().uuid(),
  items: z.array(z.object({ sku: z.string(), qty: z.number().int().positive() })),
  coupon: z.string().optional(),
  status: z.enum(["pending", "paid", "shipped"]),   // → union discriminée typée
});
type Order = z.infer<typeof Order>;   // tout le type, dérivé du schéma composé
La trousse du parseThe parse toolkit
Le schémaThe schema
z.object({…})la source uniquethe single source
.parse(x)unknown → typé, ou lèveunknown → typed, or throws
.safeParse(x)union { success } (Vol 3)union { success } (Vol 3)
Le typeThe type
z.infer<typeof S>le type, dérivéthe type, derived
SortieOutputun DTO — typé et prouvéa DTO — typed and proven
ConfianceTrustle cœur s'y fie sans recheckthe core trusts it, no recheck
Les voisins, seulement nommés.The neighbors, only named.

La famille : zod, valibot (plus léger), arktype. La coercion (z.coerce.number()) convertit les chaînes des entrées — process.env, query strings — en vrais nombres au passage. Le schéma est le type guard du 2.3, mais dérivé, donc incapable de dériver. Et le résultat porte un nom d'architecture : un DTO, la frontière franchie une bonne fois.The family: zod, valibot (lighter), arktype. Coercion (z.coerce.number()) converts input strings — process.env, query strings — into real numbers along the way. The schema is the type guard of 2.3, but derived, hence unable to drift. And the result has an architectural name: a DTO, the boundary crossed once and for all.

№ 04Le piègeThe trap4 min

Le as ApiResponse sur un .json() : zéro vérification, crash repoussé.The as ApiResponse on a .json(): zero verification, deferred crash.

C'est le contre-exemple obligatoire. interface ApiResponse + fetch().json() as ApiResponse a tout d'un code typé : une forme déclarée, un type partout en aval, un build vert. Mais aucune ligne ne regarde la donnée reçue : le as ordonne de croire, et le crash n'est que repoussé au premier champ manquant, à des écrans de la cause. À l'opposé, l'excès inverse guette aussi : parser la même donnée à chaque fonction interne. La discipline est nette — parser une seule fois, au bord, puis se fier au cœur typé.This is the mandatory counter-example. interface ApiResponse + fetch().json() as ApiResponse has every look of typed code: a declared shape, a type everywhere downstream, a green build. But no line looks at the received data: the as orders belief, and the crash is merely deferred to the first missing field, screens from the cause. At the other extreme, the opposite excess lurks too: parsing the same data at every internal function. The discipline is sharp — parse once, at the edge, then trust the typed core.

L'interface + as : une garantie qui n'existe pasThe interface + as: a guarantee that doesn't exist
interface ApiResponse { items: Item[] }

const data = await res.json() as ApiResponse;   // ✗ as : ZÉRO vérification
data.items.map(render);   // 💥 si 'items' est absent : crash au premier accès
// l'interface décrit un espoir ; le as ordonne au compilateur d'y croire.

Le as ne convertit ni ne vérifie rien (Vol 4.3) : il rebaptise un any en ApiResponse. La forme déclarée est un vœu, pas un contrôle — le runtime ne la lit jamais. C'est l'anti-parse par excellence : tout le décorum du typé, aucune de sa garantie.The as neither converts nor checks anything (Vol 4.3): it renames an any into ApiResponse. The declared shape is a wish, not a check — the runtime never reads it. It's the anti-parse par excellence: all the trappings of typed code, none of its guarantee.

Les deux façons de tricher avec un schéma.The two ways to cheat with a schema.

Avoir zod n'immunise pas. On triche encore quand on ignore l'échec — safeParse(x).data! avec un ! non-null —, ce qui est un as avec des étapes en plus. Et quand on sur-parse : revalider à chaque appel interne une donnée déjà prouvée au bord, c'est l'unknown partout du 4.2 et le sur-branding du 3.3 — de la cérémonie qui n'achète rien. Règle : valide une fois, au bord ; à l'intérieur, fais confiance au type.Having zod doesn't immunize. You still cheat when you ignore failure — safeParse(x).data! with a non-null ! — which is an as with extra steps. And when you over-parse: re-validating already-proven data at every internal call is the unknown-everywhere of 4.2 and the over-branding of 3.3 — ceremony that buys nothing. Rule: validate once, at the edge; inside, trust the type.

Une interface sur une donnée du dehors décrit ce que tu espères recevoir. Un schéma vérifie ce que tu reçois — et ne te rend que ce qui est vrai.An interface on outside data describes what you hope to receive. A schema checks what you get — and hands back only what's true.
№ 05Bilan & éditoWrap-up & editorial2 min

Transformer l'inconnu en prouvé — une fois, au bord, sans as.Turn unknown into proven — once, at the edge, no as.

Parser, ce n'est pas vérifier puis caster — c'est transformer une donnée inconnue en une donnée typée prouvée, en un geste. Le schéma est la source unique : il valide, et z.infer en dérive le type, sans dérive possible. safeParse rend une union discriminée (Vol 3) qu'on narrowe. On parse une fois, à la frontière ; le cœur typé fait ensuite confiance. C'est le réflexe le plus quotidien de la série.Parsing isn't checking then casting — it's transforming unknown data into proven typed data, in one move. The schema is the single source: it validates, and z.infer derives the type from it, no drift possible. safeParse returns a discriminated union (Vol 3) you narrow. You parse once, at the boundary; the typed core then trusts. It's the series' most everyday reflex.

Tu veux…You want to…Le gesteThe moveCe que ça coûteWhat it costs
Faire entrer une donnée du dehorsBring in outside dataschéma.parse(unknown) — valide ET type, ou lève.schema.parse(unknown) — validates AND types, or throws.Une dépendance, un peu de friction au bord.A dependency, a little friction at the edge.
Garder type et validation synchrosKeep type and validation in syncz.infer<typeof Schema> — une seule source.z.infer<typeof Schema> — a single source.Le type dérive du schéma, jamais l'inverse.The type derives from the schema, never the reverse.
Gérer l'échec proprementHandle failure cleanlysafeParse → union { success } (Vol 3).safeParse → union { success } (Vol 3).Il faut traiter la branche d'erreur.You must handle the error branch.
Ne PAS l'employerNOT use itinterface + fetch().json() as T.interface + fetch().json() as T.Un any rebaptisé — le crash est juste repoussé.A renamed any — the crash is merely deferred.
Valider répond « oui » et te laisse caster. Parser te rend la valeur, ou rien — la seule façon de franchir la frontière sans rouvrir le trou.Validating answers "yes" and lets you cast. Parsing hands you the value, or nothing — the only way to cross the boundary without reopening the hole.
Mot de l'éditeurFrom the editor

« Parser, pas valider. »"Parse, don't validate."

C'est le geste qui résout, en pratique, toute la dette de la série. Le type ne vérifie pas la donnée (Vol 2), any et as ne font que masquer (Vol 4), la frontière ment (7.1) : la conséquence est qu'au bord, on ne peut faire confiance à aucune annotation. Valider — vérifier un booléen puis caster — semble répondre au problème, mais rouvre le trou deux fois : le contrôle et le type dérivent l'un de l'autre, et le as est un pari non tenu. Parser tranche autrement : une fonction qui prend de l'unknown et rend une valeur prouvée, ou échoue franchement — avec le type tiré de la définition même qui valide.It's the move that resolves, in practice, the whole series' debt. The type doesn't check the data (Vol 2), any and as only mask (Vol 4), the boundary lies (7.1): the consequence is that at the edge, no annotation can be trusted. Validating — checking a boolean then casting — seems to answer the problem, but reopens the hole twice: the check and the type drift apart, and the as is an unkept bet. Parsing cuts differently: a function that takes unknown and returns a proven value, or fails outright — with the type drawn from the very definition that validates.

Le schéma est la source unique de vérité — le même réflexe que le discriminant (Vol 3), le brand (3.3), le mapped type (6.1), enfin posé là où il compte le plus : l'entrée des données. On valide une fois, au bord ; à l'intérieur, on tient un DTO sûr et l'on cesse de douter. Reste un dernier cas, plus fin : parfois on ne veut pas transformer une valeur, seulement vérifier qu'elle est conforme à un type — sans perdre le type précis que TypeScript a inféré. Ni un as (qui force et peut mentir), ni un parse complet. C'est satisfies, le dernier outil du volume : contrôler sans élargir.The schema is the single source of truth — the same reflex as the discriminant (Vol 3), the brand (3.3), the mapped type (6.1), finally placed where it matters most: the data entry. You validate once, at the edge; inside, you hold a safe DTO and stop doubting. One last, finer case remains: sometimes you don't want to transform a value, only to check it conforms to a type — without losing the precise type TypeScript inferred. Neither an as (which forces and can lie), nor a full parse. That's satisfies, the volume's last tool: check without widening.