Utilo

JSON para Interfaces TypeScript: Um Guia Prático para Digitar Respostas API

Aprenda a transformar JSON em interfaces TypeScript precisas, lidar com campos opcionais, nulos, uniões e matrizes e manter os tipos sincronizados com APIs reais.

· 5 min de leitura

Todos os projetos TypeScript que falam com uma API acabam enfrentando a mesma pergunta: como descrever a forma dos dados que chegam como JSON não digitado? Deixar as respostas como any joga fora o principal benefício do TypeScript. Escrever interfaces à mão para uma carga útil de 200 linhas é tedioso e propenso a erros. Este guia percorre um fluxo de trabalho confiável para gerar interfaces a partir de JSON, refiná-las e mantê-las honestas ao longo do tempo.

Por que tipos para JSON importam

Quando você chama await res.json(), o TypeScript lhe dá any. A partir desse momento, erros de digitação como o user.adress.city se compilam sem reclamações e só falham no tempo de execução, muitas vezes na produção e muitas vezes para um subconjunto de usuários cujos dados acontecem a diferir. Uma resposta digitada dá-lhe autocompletar, refatoração segura e compilar-tempo erros quando o backend renomear um campo.

Os tipos também atuam como documentação. Um novo membro da equipe que lê interface Order { id: string; items: OrderItem[]; total: number; couponCode?: string } aprende mais em cinco segundos do que de rolar através de uma resposta de amostra.

Passo 1: Começar com uma amostra real e representativa

Tipos gerados são tão bons quanto a amostra que você alimenta. Antes de converter, recolha uma resposta que exerça o maior número possível de campos:

  • Escolha um registro com campos opcionais preenchidos, como uma encomenda que tenha um cupom, um endereço de envio e uma mensagem de presente.
  • Inclua matrizes com pelo menos um elemento; uma matriz vazia não diz nada a um gerador sobre seu conteúdo.
  • Se um campo pode ser null, tente incluir um exemplo onde é nulo e um onde não é.

Copie o corpo da resposta da aba Rede do seu navegador ou do curl, paste-o em um JSON formatador e confirme que é válido. Em seguida, mude o modo para → TypeScript.

Passo 2: Gerar o primeiro rascunho

Dada esta entrada:

{
  "id": "ord_9f2",
  "createdAt": "2026-09-30T12:01:44Z",
  "customer": { "id": 42, "email": "ana@example.com" },
  "items": [
    { "sku": "TSHIRT-M", "qty": 2, "price": 19.5 },
    { "sku": "MUG", "qty": 1, "price": 12 }
  ],
  "coupon": null
}

Um gerador produz algo como:

export interface Customer {
  id: number;
  email: string;
}

export interface Item {
  sku: string;
  qty: number;
  price: number;
}

export interface Root {
  id: string;
  createdAt: string;
  customer: Customer;
  items: Item[];
  coupon: null;
}

Este é um ponto de partida sólido, mas reflete apenas o que uma amostra continha. As etapas seguintes transformá-lo em um tipo que corresponde ao contrato API.

Passo 3: Renomear e refinar

Os geradores nomeiam as interfaces aninhadas após as chaves. Renomeá-los para termos de domínio: Root torna-se Order, Item torna-se OrderItem. Em seguida, revise cada campo com estas perguntas.

** Pode estar faltando?** Se a API omitir uma chave em algumas respostas, marque-a como opcional com ?. Opcional (coupon?: Coupon) e nulavel (coupon: Coupon | null) são diferentes: o primeiro significa que a chave pode estar ausente, o segundo significa que ela está sempre presente, mas pode manter null. Muitas APIs fazem ambos, que você expressa como coupon?: Coupon | null.

** Foi null o único valor visto?** Um campo digitado literalmente como null quase sempre significa que a amostra não foi representativa. Substitui-o pelo tipo real mais | null.

Uma cadeia é realmente uma cadeia? As datas chegam como cadeias ISO 8601. Mantenha-os como string no tipo de transporte; a conversão para Date pertence a uma camada de mapeamento. Pode documentar a intenção com um alias tipo: type ISODateString = string.

Um número é um enum? Se status é sempre um dos poucos valores, use uma união de literais: status: "pending" | "paid" | "shipped". Isto dá uma verificação exaustiva do switch.

** Os IDs são seguros como números?** IDs de banco de dados acima de 2^53 perdem precisão no JavaScript. Se o seu backend usa números inteiros de 64 bits, peça-lhe para enviar IDs como strings e digite-os como string.

O resultado refinado:

export type ISODateString = string;

export interface Order {
  id: string;
  createdAt: ISODateString;
  status: "pending" | "paid" | "shipped" | "cancelled";
  customer: Customer;
  items: OrderItem[];
  coupon?: Coupon | null;
}

Passo 4: Manusear matrizes com conteúdo misto

Algumas APIs retornam matrizes heterogêneas, como um feed de atividades com diferentes tipos de eventos. Um gerador ingênuo produz uma única interface com cada campo opcional, o que perde informações. Uma união discriminatória é muito melhor:

type FeedEvent =
  | { type: "comment"; id: string; text: string }
  | { type: "like"; id: string; userId: string }
  | { type: "follow"; id: string; followerId: string };

Agora o TypeScript restringe o tipo automaticamente dentro do if (event.type === "comment"). Ao converter uma matriz mista, a ferramenta une os tipos de item inferidos; use essa saída como uma dica para projetar a união discriminada à mão.

Passo 5: Validação em tempo de execução

Tipos de TypeScript desaparecem no momento da compilação. Se o backend mudar, o código ainda recebe a nova forma e os tipos mentem silenciosamente. Para dados que cruzam um limite de confiança, adicione a validação em tempo de execução com uma biblioteca de esquemas, como Zod, Valibot ou ArkType e derive o tipo estático do esquema:

import { z } from "zod";

const OrderSchema = z.object({
  id: z.string(),
  createdAt: z.string().datetime(),
  items: z.array(z.object({ sku: z.string(), qty: z.number().int(), price: z.number() })),
  coupon: z.object({ code: z.string() }).nullable().optional(),
});

export type Order = z.infer<typeof OrderSchema>;
const order = OrderSchema.parse(await res.json());

Você ainda pode usar o gerador para bootstrap: gerar a interface, em seguida, traduzi-lo campo por campo no esquema.

Passo 6: Prefira a fonte da verdade quando existe

Se a sua API publicar um esquema OpenAPI (Swagger) ou GraphQL, gerar tipos a partir disso em vez de amostras. Ferramentas como openapi-typescript e GraphQL Code Generator produzem tipos exatos, incluindo opcionalidade e enums, e eles podem ser executados em CI para que os tipos atualizem sempre que o esquema mudar. A geração baseada em amostras brilha para APIs de terceiros sem esquemas, protótipos rápidos, webhooks e serviços legados.

Interface ou tipo?

Para formas de objetos, interface e type são quase intercambiáveis. As interfaces podem ser estendidas e mescladas, e as mensagens de erro se referem a elas pelo nome, o que ajuda com objetos grandes. Os alias de tipo são necessários para uniões, tuplas e tipos mapeados. Uma convenção comum são interfaces para formas de objetos e alias de tipo para tudo o mais.

Trapalhões comuns

  • Escrever tudo como opcional. Parece seguro, mas força verificações nulas em todos os lugares e esconde violações reais do contrato. Torne os campos opcionais apenas quando a API realmente os omite.
  • Confiança numa única amostra. Verifique a documentação ou várias respostas antes de finalizar a opção.
  • ** Mistura de transporte e tipos de domínio.** Mantenha um OrderDTO bruto que espelhe o JSON e mapeie-o em um domínio Order com objetos reais Date e campos calculados.
  • ** Esquecendo-se dos envelopes de paginação.** Muitas APIs envolvem listas em { data: [...], nextCursor: "..." }. Modela isto uma vez como um genérico: interface Page<T> { data: T[]; nextCursor: string | null }.

Uma lista de verificação rápida

  1. Captura uma amostra rica e real e valida-a.
  2. Gerar interfaces com o JSON para converter TypeScript.
  3. Renomear tipos para a linguagem do domínio.
  4. Corrigir opcional versus campos nulas, uniões literais e tipos de ID.
  5. Substitua os sindicatos mistos por sindicatos discriminados.
  6. Adicionar validação de tempo de execução para dados externos.
  7. Mudar para geração baseada em esquema quando um esquema OpenAPI ou GraphQL existe.

Seguir estas etapas leva alguns minutos por endpoint e paga muitas vezes em autocompletar, refatores mais seguros e bugs capturados antes que eles cheguem aos usuários.

Esta página foi traduzida automaticamente do inglês. Se encontrar um erro, avise-nos.

Guias relacionados