Lançamento do Zod 4
(zod.dev)- O Zod 4 foi lançado como versão estável após 1 ano de desenvolvimento, corrigindo limitações de design do Zod 3 e melhorando bastante desempenho, tamanho do bundle e eficiência de compilação do TypeScript
- O Zod 3, lançado em maio de 2021, cresceu de 2.700 estrelas no GitHub e 600 mil downloads semanais para atuais 37,8 mil estrelas e 31 milhões de downloads semanais; após 24 versões minor, melhorias importantes passaram a exigir mudanças incompatíveis
- Nos benchmarks, o parsing de strings ficou 14 vezes mais rápido, o de arrays 7 vezes, e o de objetos 6,5 vezes; as instanciações de tipo do
tscem um exemplo simples também caíram de mais de 25.000 para cerca de 175 - O tamanho do bundle principal, em gzip, caiu de
12.47kbno Zod 3 para5.36kbna versão normal do Zod 4, e o Zod Mini, com API funcional compatível com tree shaking, reduz isso para1.88kb - Os novos recursos incluem conversão para JSON Schema, registro de metadados, inferência de tipos de objetos recursivos, internacionalização, pretty-printing de erros, tipos template literal,
z.stringbool(), parâmetroerrorunificado e melhorias emz.discriminatedUnion()
Por que o Zod 4 virou uma nova major version
- O Zod 4 se tornou estável após 1 ano de desenvolvimento ativo, implementando recursos há muito pedidos sobre uma base mais rápida, menor e mais eficiente no
tsc - O Zod v3.0 foi lançado em maio de 2021 e, na época, tinha 2.700 estrelas no GitHub e 600 mil downloads semanais
- Hoje, o Zod registra 37,8 mil estrelas no GitHub e 31 milhões de downloads semanais
- Há 6 semanas, quando a beta saiu, eram 23 milhões de downloads semanais
- Com 24 versões minor acumuladas, a base de código do Zod 3 chegou ao limite, e os recursos e melhorias mais pedidos exigiam mudanças incompatíveis
- O Zod 4 fechou 9 dos 10 issues públicos mais votados
Desempenho de parsing e eficiência de compilação do TypeScript
- O Zod 4 fornece benchmarks que podem ser executados diretamente no repositório
- Melhorias de desempenho no parsing:
- Parsing de strings: 14 vezes mais rápido
- Parsing de arrays: 7 vezes mais rápido
- Parsing de objetos: 6,5 vezes mais rápido
- O benchmark de parsing de objetos usa o Moltar validation library benchmark
- Com base em
tsc --extendedDiagnostics, a compilação de um arquivo simples reduziu fortemente as instanciações de tipo- Usando
"zod/v3": mais de 25.000 vezes - Usando
"zod/v4": cerca de 175 vezes
- Usando
- O Zod 4 redesenhou e simplificou os genéricos de
ZodObjecte de outras classes de schema para evitar a explosão de instanciações - O padrão de encadear repetidamente
.extend()e.omit()levava4000mspara compilar no Zod 3, e chamadas adicionais de.extend()podiam causar o erro"Possibly infinite" - O mesmo padrão compila em
400msno Zod 4, ficando 10 vezes mais rápido - No futuro, com o compilador
tsgo, o desempenho do editor com Zod 4 pode se estender a schemas e bases de código maiores
Tamanho do bundle e Zod Mini
- O tamanho do bundle principal foi comparado empacotando um script simples de validação com
z.boolean()usandorollup - Tamanho do bundle principal em gzip:
- Zod 3:
12.47kb - Zod 4 versão normal:
5.36kb
- Zod 3:
- O bundle principal da versão normal do Zod 4 é cerca de 57% menor que o do Zod 3, uma redução de 2,3 vezes
- A API centrada em métodos do Zod é inerentemente difícil de aplicar tree shaking, e até um script simples com
z.boolean()acaba puxando implementações não usadas como.optional()e.array() - O Zod Mini oferece uma API funcional com tree shaking e correspondência 1:1 com
zod- Onde o Zod usa métodos, o Zod Mini normalmente usa funções wrapper
- Os métodos de parsing são iguais no Zod e no Zod Mini
- O método genérico
.check()adiciona refinamentos
- O Zod normal continua sendo recomendado para a maioria dos casos de uso, enquanto projetos com restrições anormalmente rígidas de tamanho de bundle podem considerar o Zod Mini
- Ao usar
zod/mini, o tamanho do bundle em gzip fica em1.88kb- Redução de 85% e 6,6 vezes em relação ao Zod 3
- Tabela comparativa: Zod 3
12.47kb, Zod 4 normal5.36kb, Zod 4 Mini1.88kb
- Mais detalhes podem ser vistos na documentação de
zod/mini
Metadados e JSON Schema
- O Zod 4 introduz um novo sistema para adicionar metadados fortemente tipados aos schemas
- Os metadados são armazenados não no próprio schema, mas em um schema registry que vincula o schema aos typed metadata
- É possível criar um registro com
z.registry(), e o método.register()do schema também permite um registro conveniente - O Zod exporta
z.globalRegistry, um registro global que recebe metadados compatíveis com JSON Schema comum - O método
.meta()adiciona convenientemente o schema aoz.globalRegistry - Para compatibilidade com o Zod 3,
.describe()continua disponível, mas no Zod 4 o recomendado é.meta() - O Zod 4 oferece conversão nativa para JSON Schema por meio de
z.toJSONSchema() - Os metadados de
z.globalRegistrysão incluídos automaticamente na saída de JSON Schema - Informações de customização do JSON Schema gerado estão na documentação de JSON Schema
Novas representações de schema e recursos de tipos
- O Zod 4 consegue inferir corretamente tipos de objetos recursivos
- É possível representar tipos recursivos e tipos mutuamente recursivos
- Diferentemente do padrão de tipos recursivos do Zod 3, não é necessário type casting
- O schema resultante é uma instância normal de
ZodObjecte pode usar todo o conjunto de métodos
- Foi adicionado um File schema para validar instâncias de
File z.templateLiteral()representa os tipos template literal do TypeScript- Tipos de schema do Zod que podem ser convertidos em string armazenam regex internos
- Os alvos incluem strings, formatos de string como
z.email(), números, boolean, bigint, enum, literal, undefined/optional, null/nullable e outros template literals - O construtor
z.templateLiteralconcatena tudo isso em uma super-regex - Formatos de string como
z.email()são corretamente aplicados, mas refinamentos customizados não são aplicados - Mais detalhes estão na documentação de template literals
- Foram adicionados novos formatos numéricos para representar inteiros de largura fixa e tipos de ponto flutuante
- Eles retornam instâncias de
ZodNumbercom as restrições adequadas de mínimo/máximo inclusivo já aplicadas
- Eles retornam instâncias de
- Também foram adicionados formatos numéricos bigint que ultrapassam o intervalo que o JavaScript
numberconsegue representar com segurança- Eles retornam instâncias de
ZodBigIntcom as restrições adequadas de mínimo/máximo inclusivo já aplicadas
- Eles retornam instâncias de
z.literal()agora pode receber opcionalmente vários valores
Erros, internacionalização e formatos de string
- O Zod 4 introduz uma nova API de
localespara traduzir globalmente mensagens de erro para vários idiomas - A lista completa de locales suportados está em Customizing errors e será atualizada conforme novos idiomas forem adicionados
- O Zod implementa a função de topo
z.prettifyError, que transformaZodErrorem uma string em formato amigável ao usuário- O formato atual não pode ser configurado
- Ele pode mudar no futuro
- Quem já usa
zod-validation-errorpode continuar usando
- Todos os formatos de string, como email, foram promovidos a funções de topo do módulo
z- Isso é mais conciso e favorece tree shaking
- A API equivalente baseada em método, como
z.string().email(), ainda pode ser usada, mas está deprecated - Ela deve ser removida na próxima major version
- A API
z.email()suporta regular expressions customizadas- Não existe um único regex canônico para email, e cada aplicação pode optar por critérios mais rígidos ou mais flexíveis
- O Zod exporta alguns regex comuns por conveniência
Coerção de boolean e simplificação da customização de erros
- O antigo
z.coerce.boolean()é uma API simples que converte valores falsy emfalsee valores truthy emtruefalse,undefined,null,0,"",NaNetc. viramfalse- Esse comportamento está alinhado com as outras APIs
z.coerce
- Para uma coerção de boolean mais sofisticada, no estilo de variáveis de ambiente, o Zod 4 introduz
z.stringbool()- É possível customizar os valores truthy e falsy
- Mais detalhes estão na documentação de
z.stringbool()
- A maior parte das mudanças incompatíveis do Zod 4 está relacionada à API de customização de erros
- A customização de erros foi unificada em um único parâmetro
errormessagefoi substituído porerror- O parâmetro
messagecontinua suportado, mas está deprecated invalid_type_errorerequired_errorforam substituídos porerrorna sintaxe de funçãoerrorMaptambém foi substituído porerrorna sintaxe de função
Composabilidade, refinamentos e transformações
z.discriminatedUnion()passou a suportar vários tipos de schema que antes não eram aceitos- Incluindo union e pipe
- Agora é possível usar um discriminated union como membro de outro discriminated union, tornando-o componível
- No Zod 3, os refinamentos eram armazenados na classe
ZodEffects, que envolvia o schema original- Essa estrutura dificultava combinar
.refine()com outros métodos de schema, como.min()
- Essa estrutura dificultava combinar
- No Zod 4, os refinamentos são armazenados dentro do schema, permitindo usar
.refine()e outros métodos de forma natural em conjunto - O novo método
.overwrite()representa transforms que não mudam o tipo inferido.transform()tem tipo de saída que não pode ser inspecionado em runtime, e a função de transformação é uma caixa-preta que pode retornar qualquer coisa- Por isso, não há uma forma segura de convertê-lo para JSON Schema
.overwrite()retorna uma instância da classe original- A função de overwrite é armazenada como refinement e não altera o tipo inferido
- Os métodos existentes
.trim(),.toLowerCase()e.toUpperCase()foram reimplementados usando.overwrite()
Base para autores de bibliotecas
- Com a adição do Zod Mini, foi criado o subpacote
zod/v4/core, que reúne os recursos centrais compartilhados entre Zod e Zod Mini zod/v4/coreexpande o Zod de uma biblioteca simples para uma base rápida de validação que pode ser embutida em outras bibliotecas- Quem estiver criando uma biblioteca de schemas pode se basear nas implementações de Zod e Zod Mini para construir sobre
zod/v4/core - Para autores de bibliotecas, há o guia For library authors
- Ele cobre best practices para construir sobre o Zod
- Responde dúvidas comuns sobre como oferecer suporte simultâneo a Zod 3, Zod 4 e Mini
1 comentários
Opiniões no Hacker News
Sou o autor; perguntem o que quiserem. Sobre a política de versões, deixei a justificativa dessa abordagem descrita com bastante detalhe: https://github.com/colinhacks/zod/issues/4371
No fim, o npm não foi projetado para lidar bem com a situação em que o Zod se encontra. Dezenas a centenas de bibliotecas importam diretamente interfaces/classes do Zod e as usam em suas APIs públicas, então, toda vez que o Zod sobe uma versão major, essas bibliotecas também precisam lançar uma nova major
Isoladamente isso é razoável, mas no Zod pode criar uma avalanche de versões dolorosa para todo mundo, e parecia provável que uma parte considerável do ecossistema ficasse presa para sempre na v3
Por isso, de forma parecida com o Go, optamos por adicionar um novo subcaminho em vez de o pacote lançar uma nova versão com quebra de compatibilidade. No ecossistema TypeScript, uma biblioteca pode ter apenas
zod@^3.25.0como peer dependency e dar suporte às duas versões ao mesmo tempo importando o que precisa de"zod/v3"e"zod/v4"Ainda assim, embora eu entenda o motivo de terem escolhido uma política de versões um tanto estranha por causa de circunstâncias especiais, do ponto de vista de equipes que não precisam se preocupar com conflitos de versões do Zod em dependências transitivas, eu gostaria que também existisse um pacote
4.0.0Se entendi corretamente, será preciso trocar os imports para
"zod/v4", o que é uma mudança bem barulhenta, e pode ser trabalhoso ter que pegar com regra de lint o problema de a IDE fazer autoimport a partir de'zod'.optional()no TypeScript está entre as 9 ou 10 principais issues resolvidas: https://github.com/colinhacks/zod/issues/635Esse foi o maior incômodo para mim no Zod, mas o Zod é tão bom que sigo usando apesar disso
Fico curioso se vocês consideram isso fora do escopo do Zod ou se simplesmente ainda não houve tempo para implementar. A discussão relacionada está em https://github.com/colinhacks/zod/discussions/2134
3.25.0, então soa meio sem sentidoVenho usando a versão alfa do Zod há meses e queria simplesmente ajustar o
package.jsonpara fazer o upgrade, mas agora parece que vou ter que vasculhar todo o histórico do gitSou muito grato pelo projeto, mas, quanto a esse feedback, acho que tornaram isso mais difícil do que precisava ser. Imagino que poderiam ter publicado também uma 4.x junto com a
3.xO trecho dizendo que “para simplificar a migração, o Zod 4 será distribuído junto com o Zod 3 como parte do release
zod@3.25, e importado pelo subcaminho"/v4"” parece um sinal de que o gerenciamento de dependências do npm está completamente bagunçadoAs peer dependencies estão tão quebradas que a v4 precisa fingir que é a v3
É verdade que o npm não lida bem com a situação em que o Zod se encontra. Dito isso, o Zod tem a restrição de praticamente não receber nenhuma biblioteca. Dezenas a centenas de bibliotecas importam diretamente interfaces/classes de
"zod"e as usam em suas APIs públicasEssas bibliotecas precisam lançar uma nova major toda vez que o Zod sobe uma versão major, e no Zod isso poderia causar uma avalanche de versões dolorosa para todos. Sinceramente, parecia que uma parte considerável do ecossistema ficaria presa para sempre na v3
Por isso, de forma semelhante ao Go, em vez de lançar uma nova versão de pacote a cada release com quebra de compatibilidade, optamos por adicionar um novo subcaminho. No ecossistema TypeScript, é possível dar suporte simultâneo a
"zod/v3"e"zod/v4"com uma única peer dependencyzod@^3.25.0Ou seja, refatorar os pontos de uso um a um para
import ... from 'zod/v4'e, quando terminar, subir completamente para a v4Pelo contrário, o npm oferece mais rotas de escape, já que permite executar várias versões ao mesmo tempo quando necessário ou sobrescrever seletivamente partes do grafo de dependências transitivas
Qual sistema de gerenciamento de pacotes resolve esse problema? Mesmo plataformas “estáveis” como Maven lidam com isso versionando em um novo namespace, como quando o Apache Commons passou da v2 para a v3
Há uma pergunta que tenho há muito tempo. Ouvi dizer que o Zod poderia se encaixar nisso, mas, mesmo olhando a documentação, não sei muito bem como fazer
Suponha que uma API de servidor retorne tipos mais refinados do que ela consegue expressar. Por exemplo,
api/:postid/authorretorna um User, mas ele pode ser um User comum ou um User anônimo, então campos comousernameelocationpodem vir como null. Nesse caso, talvez você queira representar o objeto User como uma união discriminadaObjetos vindos de outros endpoints também podem precisar de conversão de tipo. Às vezes um User inclui
Post[], e um Post pode ter propriedades especiais se for uma postagem de moderadorNo passado, escrevi funções como
normalizeUser()enormalizePost(), mas isso rapidamente ficou bagunçado. Cada endpoint retornava subconjuntos diferentes do modelo User/Post, então acabei criando umas 5 versões denormalizePost, o que ficou caótico demais. Fico curioso sobre como esse tipo de problema costuma ser resolvidoA ideia é separar objetos
successefailedcom base em um campo discriminador, comostatus. Se houver muitas propriedades especiais para moderadores, mas você não quiser listar ou verificar todas, pode definir um comportamento de passagemMesmo que vários métodos tenham esquemas diferentes, se houver uma parte comum, você pode criar um schema de objeto compartilhado e então criar objetos que adicionem campos extras a ele. Se houver muitas possibilidades, ainda vai ficar bagunçado, mas, se a situação já é bagunçada, pelo menos o validador consegue validar essa bagunça
Se não for full-stack TypeScript, por exemplo em um backend Python, você pode definir as várias formas de
Usercomo modelos Pydantic e unions, e gerar tipos de cliente TypeScript por meio da geração de schemas OpenAPI/GraphQL e geração de código"user_type"a todos os tipos dentro da union facilita lidar com os casos comuns e especiaisPor exemplo, se você verificar
user.user_type === 'authenticated', o sistema de tipos passa a saber que, depois disso, você pode usaruser.nameBibliotecas TypeScript para consultas GraphQL conseguem inferir dinamicamente a forma da resposta a partir dos campos selecionados. Se você selecionar
{ user { username posts { text } } }, o tipo de resposta teráposts; se selecionar apenas{ user { username } }, a propriedadepostsficará completamente ausenteO servidor sabe quais dados envia, então deveria fornecer essa informação ao cliente como metadados. Na prática, isso pode ser um backend Python usando schemas Pydantic, gerando automaticamente uma especificação OpenAPI com base neles, e então o cliente gerando os tipos
O Zod é muito melhor do que as alternativas que vi, mas às vezes o próprio fato de essa validação explícita ser necessária parece uma falha de onde o desenvolvimento web moderno chegou
No desenvolvimento full-stack, é frustrante demais ter que descrever a mesma forma de várias maneiras. Acabamos precisando de validação de entrada em JS, Swagger para definição de API, validação de entrada no servidor, ORM para conformidade com o schema, além de definições TypeScript separadas no servidor e no cliente, o que é tedioso
O TypeScript parece a melhor fonte única da verdade que temos, mas, na prática, as ferramentas que tentam fazer reflexão acabam redefinindo seus próprios modelos e builders. Há pelo menos 5 grandes áreas em que reflexão é central, e cada uma tem várias implementações
A antiga ferramenta de emissão de tipos do TypeScript,
reflect-metadata, fornece algumas informações de tipo em runtime, mas mira especificações muito antigas de decorators e metadados: https://www.npmjs.com/package/reflect-metadataTalvez estejamos no começo de uma unificação. O projeto Standard Schema parece ter suporte da maioria das principais bibliotecas de validação. Ainda assim, estender isso para definições de API ou ferramentas de ORM ainda está em estágio bem inicial
https://github.com/standard-schema/standard-schema?tab=readm...
Se você altera uma vez no schema Zod, isso se propaga por toda a aplicação junto com a checagem de tipos. O schema Zod acaba sendo a fonte única da verdade
Se você já tem um schema JSON Schema/Swagger, é só gerar o Zod a partir dele. Se usa um ORM TypeScript, quase certamente existe um gerador de Zod
Sinceramente, o Zod é tão amplamente usado que, para mim, virou uma definição de schema unificada da qual outras partes da stack podem depender. Como os tipos são definidos diretamente, os desenvolvedores de fato os mantêm atualizados. A documentação Swagger da empresa está sempre atrás das mudanças
O status quo só é aceitável quando você lida com clientes e empregadores que querem apenas o resultado e não se importam com o método. Nesse contexto, essas infelizes camadas de complexidade pelo menos aumentam as horas faturáveis
Claro, partindo do pressuposto de que Zod seja a biblioteca escolhida por elas para definição de schemas e validação em TypeScript
O Zod 4 parece bom, mas, mesmo incluindo as melhorias mais recentes, o ArkType ainda é várias vezes mais rápido, na casa de um dígito
Às vezes, por compatibilidade retroativa e compatibilidade de sintaxe, é difícil deixar algo tão mais rápido quanto uma biblioteca totalmente recomeçada do zero. Analisei todas essas ferramentas em um projeto recente e escolhi o ArkType em parte por esse motivo. Também achei a experiência com TypeScript melhor
Usamos Zod só para validação de formulários, então fico pensando: “por que isso importa?”. Imagino se há casos em que as pessoas validam coisas como mensagens de entrada de APIs de alta vazão, em que desempenho passa a ser mais importante
Ainda assim, acho que não vou migrar do Zod
Parabéns à equipe do Zod pelo novo lançamento. Mesmo correndo o risco de soar negativo demais, a quantidade de mudanças incompatíveis listadas no guia de migração me dá calafrios
Para um projeto que depende bastante do Zod, parece ser uma tarefa assustadora, que vai exigir muita concentração e tempo dos desenvolvedores. Isso ressoa especialmente comigo porque já mantive alguns projetos de frontend de 4 a 5 anos no trabalho
Projetos grandes em React geralmente dependem de muitas bibliotecas e, quando cada uma lança mudanças grandes, ainda mais com documentação insuficiente, tudo fica rapidamente opressor. É uma das partes que mais detesto ao trabalhar com JavaScript, e manter tudo alinhado e funcionando sem problemas parece uma subida sem fim
Sinceramente, foi um pesadelo, e pensei que teria sido melhor se tivéssemos feito em Django. Ao contrário do que eu esperava, a migração do Tailwind 3 → 4 foi uma das mais dolorosas
miniseparada. Fica mais fácil fazer uma migração gradual sem mexer no gerenciador de pacotesUsuários que não têm interesse na edição
mininão precisam se preocupar com essa parte. Mas os benefícios de tree shaking da ediçãominieram grandes demais e estavam estimulando o desenvolvimento de alternativas; o Zod precisava aceitar isso ou definharComo desenvolvedor full-stack que opera diretamente um SaaS em produção com milhares de usuários, às vezes percebo com satisfação quantos problemas simplesmente não conheço por ter decidido não criar uma SPA nem usar frameworks JS de frontend
Nunca nem pensei que algo como Zod/ArkType fosse necessário, ouvi falar pela primeira vez agora e não tenho uso para isso
É surpreendente a quantidade de esforço, complexidade e ferramentas envolvidas em criar uma SPA. Quando você decide não criar uma SPA, toda uma categoria de problemas simplesmente deixa de existir. Em vez disso, usa o navegador como ele foi originalmente projetado, com recarregamentos completos de página e foco no desenvolvimento de backend, adicionando reatividade ocasionalmente com frameworks exclusivos de backend, como Laravel Livewire
Tudo fica simples, de controle de acesso a validação e gerenciamento de estado. O app é rápido, responsivo, moderno, amigável para SEO e atende milhares de pessoas em produção
No fim, existe um schema em algum lugar. Ele pode ser definido no sistema de banco de dados ou nos modelos do ORM. Mas, em projetos grandes, colocar o schema no sistema de banco de dados ou no ORM pode criar problemas muito difíceis, então usamos Zod e Protobuf. Também acho que o Protobuf poderia ser totalmente substituído por Zod ou algo parecido
O escândalo do Post Office britânico envolveu um sistema de TI chamado Horizon: https://en.wikipedia.org/wiki/British_Post_Office_scandal
Lendo os detalhes, parece que uma validação de schema como a do Zod poderia ter ajudado a impedir esse escândalo. O apêndice técnico dos documentos judiciais da ação coletiva também menciona a ausência de uso de schemas: https://en.wikipedia.org/wiki/Bates_%26_Others_v_Post_Office...
Do jeito que está dizendo, parece que você usa um monte de
ifdefensivo. Ou, pior, talvez dependa apenas da validação de formulários HTML do lado do clienteNão sou especialista, então falo com cautela, mas achei que JSON Schema, por ser baseado em esquemas, poderia ser uma boa escolha porque permite implementar validadores também em linguagens que não sejam TypeScript
https://ajv.js.org é uma dessas bibliotecas de JSON Schema. Fico curioso para saber como ela se compara ao Zod
Se quiser, também pode ser usado como conversor para JSON. Por exemplo, é possível escrever um schema que recebe uma string, valida se ela é uma string de data ISO e produz um objeto
DateA diferença importante está no pré-processamento e no
refine. No Zod, você pode fornecer callbacks antes da validação, o que é muito útil, mas não pode ser representado em JSON. Por exemplo, antes de validar uma data, converterMM/DD/YYYYparaDD/MM/YYYYé algo mais útil com frequência do que parecePara validação, é possível usar o AJV ou o próprio sistema de validação de schemas do TypeBox. Este último é muito mais rápido, mas só oferece suporte síncrono, enquanto o AJV tem opções de validação assíncrona, como checagens em banco de dados
A reação negativa em torno deste tema é surpreendente
Testei uma versão inicial do Zod v4 e gostei da nova API, mas fiquei muito preocupado com como seria o caminho de migração. Cheguei até a pensar em sugerir que fosse publicado com um nome de pacote totalmente novo
Mas a abordagem do autor é inteligente. Ela permite que pessoas como eu adotem a v4 imediatamente sem esperar que todas as dependências sejam atualizadas
Eu tinha acabado de começar a adotar o Zod em um projeto novo, e não poderia haver momento melhor
Pelo que vejo agora, parece que haveria muita coisa para mudar para migrar para a v4