JSON para Zod
JSON para Zod gera um schema Zod executável a partir de um documento de exemplo no seu navegador. Diferente de uma interface TypeScript, a saída é código que roda: analisa um valor em tempo de execução e rejeita o que a amostra não continha. Cole vários registros para que campos opcionais e anuláveis sejam inferidos, não chutados.
O documento termina cedo demais — parece incompleto.
^
Como funciona o JSON para Zod?
Uma interface TypeScript e um schema Zod parecem semelhantes e fazem trabalhos completamente diferentes. A interface é apagada quando o código compila: diz ao seu editor como a forma deveria ser, e não diz nada ao programa em execução. Um schema Zod é um valor que sobrevive até o runtime, então pode analisar uma resposta real e rejeitá-la. Essa é a diferença entre esperar que a API cumpriu a promessa e verificar que cumpriu.
A mesma inferência, uma renderização diferente
Esta ferramenta compartilha a inferência de tipos com os cinco geradores de linguagem ao lado — uma única passada pela amostra decide quais campos são opcionais, quais são anuláveis, quais formas de objeto são idênticas e qual é o tipo do elemento de um array misto. Só a renderização difere. Isso importa porque essas decisões são a parte difícil, e uma segunda implementação acabaria discordando da primeira.
Opcional e anulável são duas coisas diferentes
{"a": null} e {} são documentos diferentes. O Zod os escreve de forma diferente também — .nullable() para o primeiro e .optional() para o segundo — e nenhum implica o outro. Uma chave ora ausente e ora null precisa dos dois, nessa ordem: z.string().optional().nullable(). Errar isso é o motivo de um validador gerado passar nos seus fixtures e falhar na segunda semana de produção.
Checagens de formato são uma afirmação sobre cada valor
A inferência percebe quando uma string parece um UUID, um email ou uma data-hora e pode emitir z.uuid(), z.email() ou z.iso.datetime() em vez de um z.string() puro. Isso é genuinamente útil e levemente perigoso: é uma afirmação sobre cada valor que o campo um dia terá, inferida de uma única amostra. Se uma coluna é um UUID no seu fixture de teste e um slug em produção, o validador rejeitará dados reais. Por isso existe o interruptor, e a tabela de referência abaixo diz qual validador cada formato detectado mapeia.
Zod 3 e Zod 4 escrevem formatos de forma diferente
O Zod 4 moveu os validadores de formato de string para o nível superior: z.uuid(), z.email(), z.url() e z.iso.datetime(), no lugar da família z.string().uuid(), agora deprecada. Ambos ainda rodam, então o alvo é uma escolha, e não um chute, e o padrão é o Zod 4 porque um schema gerado hoje vai para uma base de código que começa hoje.
Amostra JSON
{
"id": "3f2504e0-4f89-11d3-9a0c-0305e82c3301",
"email": "[email protected]",
"score": 148,
"deleted_at": null,
"tags": ["priority", "gift"]
}Schema 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>;Amostra JSON → Schema Zod
Amostra JSON
{
"id": "3f2504e0-4f89-11d3-9a0c-0305e82c3301",
"email": "[email protected]",
"score": 148,
"deleted_at": null,
"tags": ["priority", "gift"]
}Schema 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>;
Quais opções e casos-limite o JSON para Zod suporta?
| Parâmetro | Tipo | Padrão | Comportamento e casos-limite |
|---|---|---|---|
| Versão do Zod | Zod 3 | Zod 4 | Zod 4 | O Zod 4 usa z.uuid(), z.email(), z.url() e z.iso.datetime() no nível superior; o Zod 3 usa a família z.string().uuid(), que o Zod 4 ainda aceita, mas marca como deprecada. Todo o resto da saída é idêntico. |
| objetos strict | booleano | desativado | Adiciona .strict(), então uma chave que o schema não conhece vira erro em vez de ser descartada em silêncio. Vale ativar para um corpo de requisição que você está validando; em geral é errado para uma resposta de terceiros que acrescenta campos sem avisar. |
| tipos z.infer | booleano | ativado | Emite export type X = z.infer<typeof XSchema> ao lado de cada schema, para que o tipo TypeScript e o validador de runtime não se distanciem. Este é o único ponto em que o Zod é estritamente melhor que uma interface escrita à mão. |
| checagens de formato | booleano | ativado | Aplica z.uuid(), z.email(), z.url(), z.iso.datetime() e z.iso.date() onde a amostra parecia um deles. Inferir a partir de uma única amostra é uma afirmação forte — desative quando uma coluna não é homogênea em produção. |
| Opcionalidade | inferida | entre registros | Uma chave ausente em pelo menos um registro de mesma forma recebe .optional(). Cole vários registros; um registro só não consegue expressar isso, e um schema construído a partir de um único rejeitará o segundo. |
| Anulável | inferida | .nullable() | Um valor que é null em alguns registros e string em outros vira z.string().nullable(). Uma chave ora ausente e ora null recebe .optional().nullable() — dois requisitos diferentes, e não uma redundância. |
| Inteiro | inferido | z.number().int() | Um número JSON sem parte fracionária vira um schema de inteiro, então um float que chega onde a amostra tinha um número inteiro é rejeitado em vez de aceito em silêncio. Um campo que é 1 em um registro e 1.5 em outro vira um z.number() comum. |
| Array raiz | forma do documento | z.array(RootSchema) | Um array de registros no nível superior emite o schema do elemento mais um wrapper RootSchema, para que o schema do próprio documento seja uma linha em vez de algo que você precisa montar. |
| Tamanho da entrada | bytes | 2 MB | A inferência lê cada valor. Algumas centenas de registros representativos descrevem uma forma tão bem quanto um milhão, então o teto não custa nada na prática. |
Perguntas frequentes
Por que gerar um schema Zod em vez de uma interface TypeScript?
Porque um tipo TypeScript é apagado na compilação. Ele diz ao seu editor como a forma deveria ser e não diz nada ao programa em execução, então uma resposta que o viola passa direto. Um schema Zod é um valor real que roda em runtime: safeParse retorna ou os dados tipados ou os caminhos exatos que falharam. Se a API é sua e bem testada, uma interface basta. Se é de outra pessoa, ou é entrada do usuário, um validador é a única coisa que de fato confere.
Por que meu campo tem .optional() se a API sempre o envia?
Porque pelo menos um registro da amostra não o tinha. O gerador só consegue descrever os dados que recebeu. Cole uma amostra em que o campo sempre aparece para obter um schema obrigatório, ou apague o .optional() à mão — a decisão certa quando você conhece o contrato melhor que a amostra. Note que esta é a falha oposta de .nullable(): uma chave ausente e um valor null são coisas diferentes, e o schema distingue uma da outra.
.nullable() é o mesmo que .optional()?
Não, e confundi-los é o bug mais comum em schemas gerados. {a: null} tem a chave com valor null; {} não tem a chave. .nullable() aceita o primeiro, .optional() aceita o segundo, e um campo que pode ser ambos precisa dos dois — z.string().optional().nullable(). A ordem importa para a legibilidade, não para o comportamento, mas a documentação do próprio Zod escreve optional primeiro.
Por que uma string comum virou z.uuid() ou z.email()?
Porque todo valor que a inferência viu naquela posição casava com o padrão, então ela tratou o formato como parte do tipo. Isso costuma estar certo e às vezes está errado: uma coluna que tem UUIDs no seu fixture e slugs em produção agora vai falhar na validação com dados reais. Desative as checagens de formato para ter um z.string() puro em todo lugar, ou edite a linha. A tabela de referência lista exatamente qual formato mapeia para qual validador.
Qual versão do Zod devo escolher?
Zod 4, a menos que você esteja preso ao Zod 3. O Zod 4 moveu os validadores de formato para o nível superior — z.uuid() em vez de z.string().uuid() — e marcou as grafias antigas como deprecadas, então um schema gerado para o Zod 4 não tem avisos de deprecação em uma base de código nova. As chamadas antigas ainda funcionam no Zod 4, por isso um schema Zod 3 colado em um projeto Zod 4 não está quebrado, apenas datado.
Meu JSON é enviado a algum lugar?
Não. Inferência e renderização rodam na aba que você já tem aberta — abra o DevTools, observe o painel Network e cole um documento. Isso importa mais aqui do que na maioria das ferramentas, porque os documentos que as pessoas transformam em validadores são respostas de API de produção, frequentemente com registros reais de clientes.