Aller au contenu principal

JSON vers Zod

JSON vers Zod génère un schéma Zod exécutable à partir d’un document d’exemple, dans votre navigateur. Contrairement à une interface TypeScript, la sortie est du code qui s’exécute : il analyse une valeur à l’exécution et rejette ce que l’exemple ne contenait pas. Collez plusieurs enregistrements pour que les champs optionnels et nullables soient déduits plutôt que devinés.

100 % local
Version de Zod
Exemple de JSON
0 B1 ligneLig. 1, Col. 1
Erreur de syntaxe · Erreur à la ligne 1, colonne 1

Le document s’arrête trop tôt — il semble tronqué.


^
Code générélecture seule
0 B1 ligne
Si vous en avez, collez plusieurs enregistrements. Cela permet de repérer les champs facultatifs absents de certaines lignes.

Comment fonctionne JSON vers Zod ?

Une interface TypeScript et un schéma Zod se ressemblent et font des métiers complètement différents. L’interface est effacée quand le code compile : elle dit à votre éditeur quelle devrait être la forme, et ne dit rien au programme en cours d’exécution. Un schéma Zod est une valeur qui survit jusqu’à l’exécution, si bien qu’il peut analyser une vraie réponse et la rejeter. C’est la différence entre espérer que l’API a tenu sa promesse et vérifier qu’elle l’a tenue.

La même déduction, un rendu différent

Cet outil partage sa déduction de types avec les cinq générateurs de code à ses côtés — un seul parcours de l’exemple décide quels champs sont optionnels, lesquels sont nullables, quelles formes d’objets sont identiques, et quel est le type d’élément d’un tableau hétérogène. Seul le rendu diffère. Cela compte, car ces décisions sont la partie difficile, et une seconde implémentation finirait par contredire la première.

Optionnel et nullable sont deux choses différentes

{"a": null} et {} sont deux documents différents. Zod les écrit différemment aussi — .nullable() pour le premier et .optional() pour le second — et l’un n’implique pas l’autre. Une clé tantôt absente, tantôt null a besoin des deux, dans cet ordre : z.string().optional().nullable(). Se tromper là-dessus est la raison habituelle pour laquelle un validateur généré passe vos fixtures et casse dès la deuxième semaine de production.

Les contrôles de format sont une affirmation sur chaque valeur

La déduction remarque quand une chaîne ressemble à un UUID, un e-mail ou une date-heure, et peut émettre z.uuid(), z.email() ou z.iso.datetime() au lieu d’un simple z.string(). C’est réellement utile et légèrement dangereux : c’est une affirmation sur chaque valeur que ce champ contiendra un jour, déduite d’un seul exemple. Si une colonne est un UUID dans votre fixture de test et un slug en production, le validateur rejettera les vraies données. C’est une option activable pour cette raison, et le tableau de référence ci-dessous dit quel validateur correspond à chaque format détecté.

Zod 3 et Zod 4 n’écrivent pas les formats de la même façon

Zod 4 a déplacé les validateurs de format de chaînes au premier niveau : z.uuid(), z.email(), z.url() et z.iso.datetime(), en remplacement de la famille dépréciée z.string().uuid(). Les deux fonctionnent encore, si bien que la cible est un choix plutôt qu’un pari, et la valeur par défaut est Zod 4 parce qu’un schéma généré aujourd’hui rejoint un code source commencé aujourd’hui.

Exemple JSON

{
  "id": "3f2504e0-4f89-11d3-9a0c-0305e82c3301",
  "email": "[email protected]",
  "score": 148,
  "deleted_at": null,
  "tags": ["priority", "gift"]
}

Schéma Zod

export const RootSchema = z.object({
  id: z.uuid(),
  email: z.email(),
  score: z.number().int(),
  deleted_at: z.unknown(),
  tags: z.array(z.string()),
});
export type Root = z.infer<typeof RootSchema>;

Exemple JSON → Schéma Zod

Exemple JSON

{
  "id": "3f2504e0-4f89-11d3-9a0c-0305e82c3301",
  "email": "[email protected]",
  "score": 148,
  "deleted_at": null,
  "tags": ["priority", "gift"]
}

Schéma Zod

import { z } from "zod";

export const RootSchema = z.object({
  id: z.uuid(),
  email: z.email(),
  score: z.number().int(),
  deleted_at: z.unknown(),
  tags: z.array(z.string()),
});
export type Root = z.infer<typeof RootSchema>;

Quelles options et quels cas limites JSON vers Zod prend en charge ?

Référence technique et options
ParamètreTypeValeur par défautComportement et cas limites
Version de ZodZod 3 | Zod 4Zod 4Zod 4 utilise z.uuid(), z.email(), z.url() et z.iso.datetime() au premier niveau ; Zod 3 utilise la famille z.string().uuid(), que Zod 4 accepte encore mais marque comme dépréciée. Tout le reste de la sortie est identique.
objets strictsbooléendésactivéAjoute .strict(), si bien qu’une clé inconnue du schéma devient une erreur plutôt qu’un champ silencieusement ignoré. À activer pour un corps de requête que vous validez ; c’est généralement une erreur pour une réponse tierce qui ajoute des champs sans vous prévenir.
types z.inferbooléenactivéÉmet export type X = z.infer<typeof XSchema> à côté de chaque schéma, pour que le type TypeScript et le validateur à l’exécution ne puissent plus diverger. C’est le seul endroit où Zod est strictement meilleur qu’une interface écrite à la main.
contrôles de formatbooléenactivéApplique z.uuid(), z.email(), z.url(), z.iso.datetime() et z.iso.date() là où l’exemple ressemblait à l’un d’eux. Une déduction tirée d’un seul échantillon est une affirmation forte — désactivez-les quand une colonne n’est pas homogène en production.
Optionnalitédéduiteentre enregistrementsUne clé absente d’au moins un enregistrement de même forme reçoit .optional(). Collez plusieurs enregistrements ; un seul ne peut pas l’exprimer, et un schéma construit sur un seul rejettera le second.
Nullabledéduite.nullable()Une valeur null dans certains enregistrements et une chaîne dans d’autres devient z.string().nullable(). Une clé tantôt absente, tantôt null reçoit .optional().nullable() — ce sont deux exigences différentes, pas une redondance.
Entierdéduitz.number().int()Un nombre JSON sans partie fractionnaire devient un schéma entier, si bien qu’un flottant arrivant là où l’exemple avait un entier est rejeté plutôt qu’accepté en silence. Un champ qui vaut 1 dans un enregistrement et 1.5 dans un autre devient un simple z.number().
Tableau racineforme du documentz.array(RootSchema)Un tableau d’enregistrements au premier niveau émet le schéma de l’élément plus un enveloppeur RootSchema, si bien que le schéma du document lui-même tient en une ligne au lieu d’être à assembler vous-même.
Taille de l’entréeoctets2 MBLa déduction lit chaque valeur. Quelques centaines d’enregistrements représentatifs décrivent une forme aussi bien qu’un million, donc ce plafond ne coûte rien en pratique.

Questions fréquentes

Pourquoi générer un schéma Zod plutôt qu’une interface TypeScript ?

Parce qu’un type TypeScript est effacé à la compilation. Il dit à votre éditeur quelle devrait être la forme, et ne dit rien au programme en cours d’exécution : une réponse qui la viole passe donc sans encombre. Un schéma Zod est une vraie valeur qui vit à l’exécution : safeParse renvoie soit les données typées, soit les chemins exacts qui ont échoué. Si l’API est la vôtre et bien testée, une interface suffit. Si elle vient d’un tiers, ou d’une saisie utilisateur, un validateur est la seule chose qui vérifie réellement.

Pourquoi mon champ a-t-il .optional() alors que l’API l’envoie toujours ?

Parce qu’au moins un enregistrement de l’exemple ne l’avait pas. Le générateur ne peut décrire que les données qu’il a reçues. Collez un exemple où le champ apparaît toujours pour obtenir un schéma requis, ou supprimez le .optional() à la main — le bon choix quand vous connaissez le contrat mieux que l’exemple. Notez que c’est l’échec inverse de .nullable() : une clé absente et une valeur null sont deux choses différentes, et le schéma les distingue.

.nullable() et .optional(), est-ce la même chose ?

Non, et les confondre est le bogue le plus courant des schémas générés. {a: null} possède la clé, avec une valeur null ; {} ne possède pas la clé du tout. .nullable() accepte le premier cas, .optional() le second, et un champ qui peut être l’un ou l’autre a besoin des deux — z.string().optional().nullable(). L’ordre compte pour la lisibilité plutôt que pour le comportement, mais la documentation de Zod écrit elle-même optional en premier.

Pourquoi une simple chaîne est-elle devenue z.uuid() ou z.email() ?

Parce que chaque valeur observée à cette position correspondait au motif, la déduction a donc traité le format comme partie du type. C’est généralement juste et parfois faux : une colonne qui contient des UUID dans votre fixture et des slugs en production échouera maintenant à la validation sur les vraies données. Désactivez les contrôles de format pour obtenir un simple z.string() partout, ou modifiez la ligne en question. Le tableau de référence liste exactement quel format correspond à quel validateur.

Quelle version de Zod cibler ?

Zod 4, sauf si votre projet est épinglé sur Zod 3. Zod 4 a déplacé les validateurs de format au premier niveau — z.uuid() au lieu de z.string().uuid() — et a déprécié les anciennes écritures, si bien qu’un schéma généré pour Zod 4 ne produit aucun avertissement de dépréciation dans un nouveau code source. Les anciens appels fonctionnent encore dans Zod 4 : un schéma Zod 3 collé dans un projet Zod 4 n’est pas cassé, juste daté.

Mon JSON est-il envoyé quelque part ?

Non. La déduction et le rendu s’exécutent tous deux dans l’onglet déjà ouvert — ouvrez les DevTools, surveillez le panneau Réseau et collez un document. C’est plus important ici que sur la plupart des outils, car les documents que l’on transforme en validateurs sont des réponses d’API de production, souvent truffées de vraies données clients.