Anúncios incomodam? Ir Sem anúncios Hoje

JSON para TypeScript Gerar interfaces automaticamente a partir de respostas de API

Atualizado em

Escrever manualmente interfaces do TypeScript para cada resposta de API é tedioso e propenso a erros. Aprenda a gerar automaticamente interfaces precisas a partir de dados JSON reais, e depois adicione validação em tempo de execução com Zod — porque os tipos desaparecem em tempo de execução e `any` não é uma solução.

JSON para TypeScript: Gerar interfaces automaticamente a partir de respostas de API 1
ANUNCIADO Remover?

Você acabou de buscar dados de uma API de terceiros. A resposta é um bloco denso de JSON — objetos aninhados, arrays, campos nulos — e agora você precisa descobrir como tipar isso em TypeScript. Então abre um novo arquivo e começa a digitar interface User { ... }, e 20 minutos depois você já tem algo que provavelmente corresponde aos dados reais. Provavelmente.

Existe uma maneira melhor. Ferramentas que convertem JSON diretamente em interfaces de TypeScript reduzem esse trabalho de 20 minutos para segundos. Este artigo explica como os tipos gerados parecem, como lidar com casos difíceis (valores nulos, uniões, aninhamento profundo) e por que você deve combinar tipos gerados com esquemas do Zod para detectar desvios de forma em tempo de execução — não apenas em tempo de compilação.

Por que interfaces do TypeScript para respostas de API são importantes

A proposta do TypeScript é detectar erros antes que o código execute. Sem respostas tipadas de API, você está perdido: acessando propriedades que podem não existir, tratando valores opcionais como obrigatórios ou convertendo silenciosamente uma string "null" para algo inesperado no downstream.

Considere esta situação comum:

const user = await fetchUser(id);
console.log(user.address.city); // TypeError at runtime if address is null

Se você tivesse tipado a resposta corretamente — com address: Address | null — o TypeScript teria marcado imediatamente esse acesso. O compilador é sua primeira linha de defesa, mas apenas se você fornecer algo para que ele trabalhe.

Escrever interfaces manualmente para cada API é tedioso e propenso a erros. Você mal interpreta o esquema, esquece um campo opcional ou copia e cola uma versão obsoleta. Gerar interfaces diretamente a partir de dados JSON remove esse erro humano da equação.

O que a conversão JSON para TypeScript produz

Considere uma resposta de API simples:

{
  "id": 42,
  "username": "jsmith",
  "email": "j@example.com",
  "createdAt": "2024-01-15T10:30:00Z",
  "role": "admin",
  "profile": {
    "bio": "Developer",
    "avatar": null
  }
}

Cole isso no Gerador de Interfaces TypeScript a partir de JSON e você obtém:

interface Profile {
  bio: string;
  avatar: null;
}

interface RootObject {
  id: number;
  username: string;
  email: string;
  createdAt: string;
  role: string;
  profile: Profile;
}

Alguns pontos a notar:

  • Objetos aninhados se tornam interfaces própriasProfile é extraído automaticamente em vez de ser incluído.
  • Datas são tipadas como string — o JSON não tem um tipo de data, então as strings ISO permanecem como strings. Você precisará parsear essas strings por si só.
  • avatar: null é tipada como literal null — o que é preciso, mas incompleto. Mais sobre isso abaixo.

Lidando com casos difíceis

Campos nulos

Quando um campo é null no seu JSON de amostra, o gerador tipa como null. Mas na prática, esse campo provavelmente muda entre um valor real e nulo, dependendo dos dados. Você vai querer ajustar manualmente:

// Generated
avatar: null;

// What you actually want
avatar: string | null;

O mesmo vale para campos opcionais que, por acaso, estão populados na sua amostra — adicione ? a qualquer propriedade que pode estar ausente em algumas respostas.

Matrizes

Arrays de objetos são tratadas de forma limpa. Dado:

{
  "posts": [
    { "id": 1, "title": "Hello", "published": true },
    { "id": 2, "title": "World", "published": false }
  ]
}

O gerador produz:

interface Post {
  id: number;
  title: string;
  published: boolean;
}

interface RootObject {
  posts: Post[];
}

Tipos de União

Se seus exemplos de JSON mostram um campo com tipos diferentes em registros distintos — digamos, um value que pode ser um número ou uma string — você deve representar isso como uma união. Os tipos gerados não capturam isso a partir de uma única amostra, então vale a pena verificar contra a documentação da API:

value: string | number;

Aninhamento profundo

O aninhamento profundo é onde o tipagem manual realmente desmorona — e onde os geradores ganham seu valor. Uma resposta com três ou quatro níveis de aninhamento é decomposta em uma hierarquia limpa de interfaces nomeadas, cada uma responsável por sua própria forma.

A lacuna entre tipos em tempo de compilação e a realidade em tempo de execução

Aqui está o que muitos iniciantes em TypeScript frequentemente ignoram: os tipos desaparecem em tempo de execução. O TypeScript compila para JavaScript, e o JavaScript não tem conceito de interfaces. Se a API retornar uma forma que não corresponde ao tipo declarado, o TypeScript não saberá — e não te avisará.

Isso torna o padrão comum de casting de respostas de API realmente perigoso:

const data = await response.json() as User; // No validation, just trust

Esse casting diz ao TypeScript “confie em mim, isso é um User” — mas o TypeScript não tem forma de verificar isso. Se a API mudar sua forma ou retornar um objeto de erro em vez disso, seu código quebra em tempo de execução de maneiras que o compilador nunca te avisou.

A solução é validação em tempo de execução.

Zod para validação em tempo de execução

Zod é uma biblioteca de validação de esquemas com foco em TypeScript. Você define um esquema uma vez, usa para analisar dados recebidos e obtém um valor totalmente tipado — ou um erro detalhado se a forma não corresponder. Sem casting, sem suposições.

Usando o mesmo exemplo de JSON anterior, o Gerador de Esquema Zod de JSON produz:

import { z } from "zod";

const ProfileSchema = z.object({
  bio: z.string(),
  avatar: z.null(),
});

const RootObjectSchema = z.object({
  id: z.number(),
  username: z.string(),
  email: z.string(),
  createdAt: z.string(),
  role: z.string(),
  profile: ProfileSchema,
});

type RootObject = z.infer<typeof RootObjectSchema>;

Observe a última linha: z.infer deriva o tipo do TypeScript diretamente do esquema. Você obtém segurança de tipo em tempo de compilação e validação em tempo de execução a partir de uma única fonte de verdade.

Usando isso na fronteira do fetch parece assim:

const rawData = await response.json();
const user = RootObjectSchema.parse(rawData); // throws if shape is wrong

// Or use safeParse to avoid throwing:
const result = RootObjectSchema.safeParse(rawData);
if (!result.success) {
  console.error(result.error.issues);
} else {
  console.log(result.data.username); // fully typed
}

Ajuste o esquema gerado para lidar com casos nulos que o gerador não pode inferir a partir de uma única amostra:

avatar: z.string().nullable(), // was z.null()
bio: z.string().optional(),    // if the field might be absent

interface vs type: Qual você deve usar?

Ambos interface e type aliases podem representar formas de objetos no TypeScript, e para a maioria das tipagens de respostas de API, eles são intercambiáveis. As diferenças práticas:

  • Interfaces podem ser estendidas e mescladas — útil se você quiser aprimorar um tipo base em vários arquivos. A mesclagem de declarações permite adicionar campos a uma interface definida em outro lugar.
  • Aliases de tipo são mais flexíveis — eles podem representar uniões, interseções, tuplas e tipos mapeados, o que interfaces não podem.
  • As mensagens de erro tendem a ser mais claras com interfaces — o TypeScript expande aliases de tipo nas mensagens de erro, o que pode tornar erros profundamente aninhados mais difíceis de ler.

Para formas de respostas de API, qualquer um funciona. Escolha interface se você antecipar a extensão do tipo; use type se você precisar de semânticas de união ou interseção. A consistência dentro de um repositório de código é mais importante do que qual escolher.

O fluxo prático

Aqui está um fluxo que leva minutos em vez de uma tarde:

  1. Obtenha uma resposta real. Use a aba de rede do navegador, Postman ou curl para capturar uma resposta real de uma API. Quanto mais completa for a amostra, melhor serão os tipos gerados.
  2. Gerar a interface do TypeScript. Cole o JSON no Gerador de Interfaces TypeScript a partir de JSON. Copie a saída para seu projeto.
  3. Gerar o esquema do Zod. Cole o mesmo JSON no Gerador de Esquema Zod de JSON. Copie isso também para seu projeto.
  4. Revise os campos nulos e opcionais. Revise a saída gerada para campos tipados como literal null ou campos que podem estar ausentes. Atualize esses para string | null, .nullable(), ou .optional() quando necessário.
  5. Valide na fronteira do fetch. Substitua quaisquer as YourType por YourSchema.parse() ou safeParse(). Agora o tipo é garantido para corresponder, e não apenas assumido.

Isso é o ciclo completo — da resposta bruta de JSON até segurança em tempo de compilação e garantias em tempo de execução em alguns minutos.

Quer eliminar anúncios? Fique sem anúncios hoje mesmo

Instale nossas extensões

Adicione ferramentas de IO ao seu navegador favorito para acesso instantâneo e pesquisa mais rápida

Ao Extensão do Chrome Ao Extensão de Borda Ao Extensão Firefox Ao Extensão Opera

O placar chegou!

Placar é uma forma divertida de acompanhar seus jogos, todos os dados são armazenados em seu navegador. Mais recursos serão lançados em breve!

ANUNCIADO Remover?
ANUNCIADO Remover?
ANUNCIADO Remover?

Notícias com destaques técnicos

Envolver-se

Ajude-nos a continuar fornecendo ferramentas gratuitas valiosas

Compre-me um café
ANUNCIADO Remover?