§

Amostra JSON

A preparar o inferidor de tipos…
§

Interfaces TypeScript

TypeScript

As equipas TypeScript em Portugal e no Brasil deparam-se com este problema cedo. Os grandes SDKs incluem clientes tipados (Stripe, Twilio, AWS), mas os serviços internos e os payloads de webhook de terceiros raramente os têm. O fluxo de trabalho típico é: capturar uma resposta real no painel de rede, colar aqui, dar o nome da raiz ao endpoint e copiar a saída para o diretório de tipos do projeto. A partir daí, o modo strict apanha as discrepâncias que a documentação se esqueceu de mencionar. O inferidor corre inteiramente no seu navegador, por isso payloads de APIs de staging, corpos de webhook assinados e endpoints não lançados nunca chegam a um serviço alojado.

Como funciona a inferência JSON para TypeScript

A inferência é uma única passagem sobre a árvore JSON analisada. A ferramenta lê cada valor, escolhe um tipo TypeScript para ele e depois escreve uma interface por cada objeto que encontrou.

  1. Analisar a amostra JSON com o analisador nativo do navegador e rejeitar entradas malformadas com uma indicação de linha/coluna.
  2. Detectar um tipo TypeScript para cada valor — string, number, boolean, null, array ou objeto aninhado.
  3. Dar a cada objeto aninhado um nome de interface derivado da chave da propriedade pai (por isso user.address torna-se uma interface Address).
  4. Fundir tipos de itens em cada array para que uma lista de {id: 1} e {id: 2, label: "x"} produza uma união com os campos opcionais corretos.
  5. Aplicar as opções (interface vs. type, readonly, opcional-nulo) e emitir declarações por ordem de dependência para que o ficheiro compile sem referências a frente.

Por que gerar tipos TypeScript a partir de JSON?

  • A maioria dos erros de forma pode ser detetada em tempo de compilação se o tipo de resposta estiver escrito. Inferir uma interface a partir de um payload real escreve a maior parte por si, e o modo `strict` apanha o campo que a documentação se esqueceu de mencionar.
  • Combinar interfaces inferidas com um validador em tempo de execução como Zod ou io-ts dá à mesma forma dois trabalhos: autocomplete no editor durante o desenvolvimento e um 400 na borda quando a produção envia algo inesperado.
  • O servidor de linguagem TypeScript só expõe os campos que conhece. Assim que importar a interface inferida, o autocomplete funciona no momento em que escreve o ponto — sem mais casts `as any` na resposta e pesquisas frustradas pelo repositório.
  • Se está prestes a escrever uma especificação OpenAPI, uma interface inferida é um primeiro rascunho rápido do esquema de resposta. Ainda vai querer exemplos escritos à mão e restrições, mas os nomes e tipos de propriedade já estão corretos.

Aplicações comuns

A inferência é mais útil quando existe um payload real mas não existe um esquema.

  • Tipificar payloads de webhook de terceiros do Stripe, GitHub ou Twilio antes de escrever um handler.
  • Criar tipos iniciais para uma API REST interna para que a equipa de front-end possa começar a programar contra ela no mesmo dia em que o back-end fica pronto.
  • Gerar um ponto de partida para um esquema Zod, io-ts ou Valibot a partir de uma resposta de API observada.

Como fica a saída?

Dado um documento JSON de amostra e um nome de raiz, o gerador produz uma árvore de interfaces, uma por objeto aninhado. Para a entrada abaixo com o nome de raiz User:

Cole {"id":1,"name":"Alice","tags":["a","b"],"address":{"city":"Paris"}} com o nome de raiz User e o gerador produz:

export interface User {
  id: number;
  name: string;
  tags: string[];
  address: Address;
}

export interface Address {
  city: string;
}
Note que address foi promovido para a sua própria interface com nome — essa é a saída por ordem de dependência. O mesmo JSON com o estilo de declaração type emitiria export type User = {...}; com o interruptor readonly ativo, cada propriedade recebe o modificador readonly.

Opções do gerador

Estilo de declaração

Escolha interface (o idioma TypeScript padrão para formas de objetos) ou type (útil se posteriormente precisar de tipos mapeados, tipos condicionais ou interseções). Ambos produzem comportamento em tempo de execução idêntico; a escolha é uma preferência de estilo de código.

Campos nulos opcionais

Quando um valor amostrado é null, o tipo do campo torna-se T | null. Ativar esta opção também adiciona um modificador ? para que o campo seja opcional do lado TypeScript — útil quando a API por vezes omite a chave completamente em vez de devolver null.

Modificador readonly

Adiciona readonly a cada declaração de propriedade para que a interface emitida corresponda a um modelo de dados imutável. Útil para fatias de estado Redux, respostas de API congeladas ou qualquer lugar onde pretende que o compilador assinale mutações acidentais.

Suporta objetos aninhados e arrays?

Sim. Cada objeto aninhado torna-se uma interface com nome derivado da chave da propriedade pai, e os arrays inferem o tipo de item a partir do seu conteúdo. Arrays de objetos recebem uma interface por forma de objeto, com tipos de união onde as formas divergem.

Como são inferidos os campos opcionais?

Ative o interruptor "Marcar campos nulos como opcionais" e qualquer campo cujo valor amostrado seja null recebe um modificador ? na chave mais | null no tipo. Sem o interruptor, o campo continua obrigatório e o tipo é simplesmente T | null.

Suporta uniões discriminadas?

Tipos de união básicos aparecem quando um array contém itens de formas mistas ou quando um campo tem simultaneamente um valor e null. A inferência de união discriminada completa (escolher type ou kind como a etiqueta e dividir as variantes) precisa de múltiplas amostras — está planeada mas não está na versão atual.

Posso inferir tipos de múltiplas amostras JSON?

Ainda não — o inferidor atual lê uma amostra de cada vez. Se tiver dois payloads que devem partilhar uma interface (por exemplo, um endpoint de lista e um endpoint de item único), a solução prática é juntá-los num array, gerar a partir daí e depois renomear os tipos de união resultantes. A inferência multi-amostra está no roteiro porque é a única forma de detetar campos presentes numa resposta e ausentes noutra.

Cole um payload, dê um nome à raiz, copie as interfaces. Todo o pipeline corre no seu navegador, por isso uma API não lançada ou um corpo de webhook assinado fica na sua máquina.