Java Script

O que é o Zod?

O que é o Zod? – O Zod é uma biblioteca de declaração e validação de dados, ou “schema validation” em TypeScript. Dessa forma, é possível criar uma estrutura com os requisitos de dados que você deseja validar.

O interessante ao usar o Zod é que ele permite definir tipos de dados utilizando uma sintaxe concisa, garantindo que apenas dados válidos sejam aceitos, o que assegura a segurança do código. Além disso, possui integração com o TypeScript, permitindo o uso de tipos estáticos e inferência de tipos.

Vamos instalar o Zod e criar um exemplo para compreender como o Zod funciona. Para isso, é necessário, primeiramente, criar uma pasta e executar o comando:

npm init -y

Desta forma vamos inicializar um novo projeto e será criado o arquivo package.json.

Agora podemos instalar o TypeScript executando o comando:

npm i --dev typescript

Por fim vamos executar o comando para criar o arquivo tsconfig.json:

npx tsc --init

Neste momento podemos fazer a instalação do Zod propriamente dita:

npm install zod

Perfeito, agora podemos criar um exemplo.

Utilizando o Zod para validação de dados.

O primeiro passo é criar o “schema”, ou seja, a estrutura que verificará se os dados que estamos tentando validar são válidos. Podemos criar um “schema” que valide os dados de um objeto da seguinte forma:

import { z } from "zod";

const carroSchema = z.object({
  modelo: z.string(),
  ano: z.number().min(1990).max(2023),
  fabricante: z.string(),
  cor: z.string().optional()
});

Veja que podemos criar uma estrutura onde temos os atributos e os tipos que esses atributos devem ter. No atributo “ano”, além do tipo, definimos que deve ser um número no intervalo de 1990 até 2023. Outro ponto é o atributo “cor”, que pode ser opcional. Ou seja, o Zod permite que, além de definir a estrutura de tipos, também possamos criar validações mais específicas e complexas.

Agora, vamos completar este arquivo recebendo um objeto “carro” e validando se as informações estão corretas e como o Zod se comporta:Copiar

import { z } from "zod";

const carroSchema = z.object({
  modelo: z.string(),
  ano: z.number().min(1990).max(2023),
  fabricante: z.string(),
  cor: z.string().optional()
});

const carro = {
  modelo: "Fiesta",
  ano: 2000,
  fabricante: "Ford",
  cor: "Azul",
};

const validationResult = carroSchema.safeParse(carro); // 

if(!validationResult.success) {
  console.log(validationResult.error);
} else {
  console.log(validationResult.data);
}

O método safeParse() retorna um objeto com a seguinte estrutura caso a validação ocorra com sucesso:Copiar

{
  success: true,
  data: { modelo: 'Fiesta', ano: 2000, fabricante: 'Ford', cor: 'Azul' }
}

Ou caso ocorra um erro de validação:

{ success: false, error: [Getter] }

Ou seja, podemos utilizar a constante validation Result e tratar caso ocorra algum problema, como fizemos acima utilizando o condicional if, desta forma, caso success seja falso, vamos obter o seguinte retorno na propriedade error:

[
  {
    "code": "invalid_type",
    "expected": "number",
    "received": "string",
    "path": [
      "ano"
    ],
    "message": "Expected number, received string"
  }
]

Neste caso, o campo “ano” recebeu uma string em vez de um dado do tipo number. Observe que o Zod nos informa exatamente o que ocorreu, o que é muito interessante!

Dessa forma, você pode tratar os erros da maneira que for necessária para a sua aplicação. Outra observação importante é que há o método parse(), no entanto, esse método lança uma exceção caso a validação não ocorra com sucesso.

Podemos também diretamente no carroSchema definir mensagens customizadas para a nossa validação e utilizar a inferência de tipo para criar o tipo carro já com as validação, veja como:

import { z } from "zod";

const carroSchema = z.object({
  modelo: z.string({
    required_error: "modelo é obrigatório",
    invalid_type_error: "modelo deve ser uma string",
  }),
  ano: z.number({
    required_error: "ano é obrigatório",
    invalid_type_error: "ano deve ser um número"
  }).min(1990).max(2023),
  fabricante: z.string({
    required_error: "fabricante é obrigatório",
    invalid_type_error: "fabricante deve ser uma string"
  }),
  cor: z.string({
    invalid_type_error: "cor deve ser uma string"
  }).optional()
});

type ValidateCarroPayload = z.infer<typeof carroSchema>;

const carro: ValidateCarroPayload = {
  modelo: "Fiesta",
  ano: 2005,
  fabricante: "Ford",
  cor: "Prata"
}

const result = carroSchema.safeParse(carro)
console.log(result);

Se a validação ocorrer com sucesso:Copiar

{
  success: true,
  data: { modelo: 'Fiesta', ano: 2005, fabricante: 'Ford', cor: 'Prata' }
}

Validações

Quando estamos trabalhando com validações personalizadas podemos deixar as validações mais complexas, como por exemplo validar se dois campos são iguais, reaproveitando o exemplo acima, podemos adicionar o campo renavam para demostrar essa validação:

import { z } from "zod";

const carroSchema = z.object({
  modelo: z.string({
    required_error: "modelo é obrigatório",
    invalid_type_error: "modelo deve ser uma string",
  }),
  ano: z.number({
    required_error: "ano é obrigatório",
    invalid_type_error: "ano deve ser um número"
  }).min(1990).max(2023),
  fabricante: z.string({
    required_error: "fabricante é obrigatório",
    invalid_type_error: "fabricante deve ser uma string"
  }),
  renavam: z.number({
    required_error: "renavam é obrigatório",
    invalid_type_error: "renavam deve ter somente números",
  }),
  renavamConfirmacao: z.number(),
  cor: z.string({
    invalid_type_error: "cor deve ser uma string"
  }).optional()
}).refine((data) =>
data.renavam === data.renavamConfirmacao, { message: "Renavam não está igual" });

type ValidateCarroPayload = z.infer<typeof carroSchema>;

const carro: ValidateCarroPayload = {
  modelo: "Fiesta",
  ano: 2015,
  fabricante: "Ford",
  cor: "Prata",
  renavam: 123456,
  renavamConfirmacao: 321654,
}

const result = carroSchema.safeParse(carro)

if(!result.success) {
  console.log(result.error.errors);
} else {
  console.log(result.success);
}

Seguindo a mesma lógica do exemplo acima, ao tratar o retorno dentro do condicional if para exibir os erros (acessando a propriedade errors), no caso de ocorrer algum problema na validação, vamos obter o seguinte resultado:

[
  {
    "code": "custom",
    "message": "Renavam não está igual",
    "path": []
  }
]

Veja que apontou o erro na validação da propriedade renavam.

Trabalhando com “inputs” e “outputs”

Um recurso interessante do Zod é a possibilidade de trabalhar com tipos diferentes de entrada e saída, mas como isso funciona?

const numeroDeCaracteres = z.string().transform((palavra) => palavra.length);

type PalavraEntrada = z.input<typeof numeroDeCaracteres>;
type PalavraSaida = z.output<typeof numeroDeCaracteres>

const palavra: PalavraEntrada = 'Zod';
const numeroDeLetras: PalavraSaida = palavra.length;

console.log(palavra, numeroDeLetras); // Zod 3

console.log(numeroDeCaracteres.parse(palavra)); // 3

Veja que temos a constante numeroDeStrings, essa constante recebe uma string que será convertida em um number. Podemos utilizar o recurso input e output para criar tipos diferentes a partir dessa constante e então aplicar conforme as necessidades da nossa aplicação e garantir que estamos trabalhando com os tipos corretos.

Outro recurso que podemos utilizar é sobre o método parse() direto na constante palavra, que recebe uma string e irá retornar, neste caso, um number referente ao número de caracteres da palavra, conforme atribuido na constante numeroDeCaracteres.

Conclusão

Este artigo é uma introdução básica à biblioteca de declaração e validação de dados Zod, onde aprendemos a instalar e utilizar os recursos básicos de validação de forma rápida e clara. Caso você queira aprofundar-se mais na utilização do Zod, aconselho verificar diretamente a documentação da biblioteca.

Veja quais são os tópicos abordados durante o curso de TypeScript – Fundamentos:

  • Compreender melhor as vantagens que a utilização do TypeScript pode trazer;
  • Utilizar a tipagem estática que o TypeScript traz;
  • Utilizar o conceito de type assertion;
  • Aplicar construção e desconstrução (ou decomposição) de objetos no TypeScript;
  • Aplicar conceitos típicos de orientação a objetos, como classes, métodos acessores, interfaces e herança com o TypeScript;
  • Verificar qual seria o código JavaScript que teria que ser produzido para se obter um efeito análogo à utilização do TypeScript.