Fundamentos do Zod
Três bugs até agora, todos com o mesmo formato. Um valor chegou, o código o usou, e ninguém o conferiu antes.
Corrigir cada um deles no lugar onde aparece funciona, mas é uma tarefa sem fim. Todo destino novo é mais um lugar para lembrar de checar.
A outra abordagem é conferir o valor uma única vez, assim que ele chega, contra uma descrição do que você esperava receber. Essa descrição é um schema, e o Zod é a biblioteca que esta seção usa para escrever um.
O que é um schema
Imagine o scanner de um aeroporto. Sua mala entra por um lado. A máquina foi configurada com regras: nada de cortantes, nada de líquidos acima de certo tamanho. Uma mala que satisfaz as regras sai do outro lado sem alterações. Uma mala que não satisfaz nunca passa.
Um schema é a configuração da máquina. Fazer o parse é passar a mala pelo scanner.
Instale e importe:
npm install zodimport * as z from 'zod'O schema mais simples é uma única regra sobre um único valor:
const teacherSchema = z.string()
const teacher = 'Jonathan'
console.log(teacherSchema.parse(teacher))
// 'Jonathan'parse entrega o dado para a máquina. O dado satisfaz a regra, então ele volta intacto.
Passe algo que não se encaixa e a máquina para:
teacherSchema.parse(12345)
// ZodError: Invalid input: expected string, received numberEsse é o modelo inteiro. Descreva o que é aceitável, passe valores por ali, receba o valor de volta ou receba um erro.
O schema é a descrição do que você vai aceitar. O parse é o ato de conferir algo contra ele. Você escreve o schema uma vez e faz o parse com ele quantas vezes quiser.
Descrevendo um objeto
Um professor raramente é apenas uma string. Em vez disso, dê ao schema um formato:
const teacherSchema = z.object({
name: z.string(),
age: z.number(),
})
const teacher = {
name: 'Jonathan',
age: 21,
}
console.log(teacherSchema.parse(teacher))
// { name: 'Jonathan', age: 21 }Cada entrada dentro de z.object() é uma chave, e seu valor é o schema daquela chave. Agora a máquina espera um objeto com um name que seja qualquer string e um age que seja qualquer número.
Quebre um deles e o erro diz exatamente qual:
teacherSchema.parse({ name: 'Jonathan', age: '21' })
// ZodError: Invalid input: expected number, received stringO Zod já vem com os tipos primitivos que você esperaria, z.string(), z.number(), z.boolean(), e objetos se aninham dentro de objetos tão profundamente quanto seus dados exigirem.
Assim, a mesma ideia cobre tanto um formulário de dois campos quanto uma resposta de API profundamente estruturada. Você está sempre descrevendo um nível de cada vez.
Por que a checagem precisa acontecer em tempo de execução
O TypeScript também descreve formatos:
type Teacher = {
name: string
age: number
}Isso parece com o schema e cumpre uma função completamente diferente. O TypeScript verifica tipos enquanto você escreve o código. Quando ele compila para JavaScript, toda anotação é apagada, então nada sobrevive no programa em execução.
O que é ótimo para valores que seu próprio código produziu, e inútil para valores que ele não produziu. Um corpo de requisição, uma resposta de API, um envio de formulário: essas coisas chegam enquanto o programa está rodando, muito depois de os tipos terem deixado de existir. O TypeScript assume que seus dados estão corretos. O Zod confere.
Um schema é a descrição de um tipo que ainda existe no momento em que o dado aparece.
O TypeScript é uma conversa com você enquanto você escreve. O schema é uma conversa com o dado enquanto o programa roda.
Mão na massa
Escreva do zero, sem copiar o exemplo do professor.
- Importe o Zod.
- Crie um
characterSchemacom duas chaves:name, uma string, eepisode, um número. - Defina um objeto
charactercom o nome'Luke Skywalker'e episódio4. - Valide o character contra o schema e exiba o resultado no console.
- Mude
episodepara a string'4'e preveja o que vai acontecer antes de rodar o código.
Compare suas respostas
import * as z from 'zod'
const characterSchema = z.object({
name: z.string(),
episode: z.number(),
})
const character = {
name: 'Luke Skywalker',
episode: 4,
}
console.log(characterSchema.parse(character))
// { name: 'Luke Skywalker', episode: 4 }Duas chaves significam z.object() em vez de um primitivo isolado, e cada chave recebe seu próprio schema.
Com episode como '4', parse lança ZodError: Invalid input: expected number, received string. A string '4' não é um número, e o Zod não vai convertê-la silenciosamente para você. Converter de propósito é uma instrução separada, coberta no próximo capítulo.
Episódio 4 está correto, por sinal. Luke Skywalker aparece pela primeira vez no filme original de Star Wars de 1977, que mais tarde foi numerado como Episódio IV.
Para onde isso vai a seguir
Por enquanto, um schema faz uma única coisa: aceita um valor ou lança uma exceção. Isso já basta para descrever dados, mas ainda não basta para construir algo com eles.
Inferindo tipos e convertendo entradas adiciona as duas peças que tornam isso prático. Um único schema pode entregar o tipo também ao TypeScript, de modo que o formato seja escrito apenas uma vez. E um parse malsucedido pode te devolver um resultado para inspecionar, em vez de uma exceção para capturar.

