Todo desenvolvedor TypeScript já passou por isso: você está integrando uma nova API, tem uma resposta de exemplo e precisa de interfaces tipadas antes de poder escrever uma única linha de lógica de negócio. Escrever essas interfaces na mão é tedioso, repetitivo e propenso a erros — principalmente quando a resposta é profundamente aninhada ou tem dezenas de campos. Este guia cobre todas as abordagens práticas para converter JSON em interfaces TypeScript: como a saída deve ser, como gerá-la automaticamente e como lidar com casos complicados.
Por que interfaces TypeScript importam para dados JSON
Quando você chama uma API em TypeScript sem anotações de tipo, a resposta parseada é any. Isso significa sem autocompletar, sem checagem de tipos e sem segurança em tempo de compilação. Um typo no nome de uma propriedade vira bug em tempo de execução em vez de erro de build.
// Sem tipos — tudo é any
const response = await fetch('/api/users/42');
const user = await response.json(); // user: any
console.log(user.naem); // typo — sem erro em tempo de compilação
// Com uma interface tipada
interface User {
id: number;
name: string;
email: string;
}
const response = await fetch('/api/users/42');
const user: User = await response.json();
console.log(user.naem); // Error: Property 'naem' does not exist on type 'User'A interface não adiciona custo de execução — só existe em tempo de compilação. Mas muda completamente a experiência de desenvolvimento.
O que uma conversão JSON-para-TypeScript produz
Todo objeto JSON vira uma interface TypeScript, cada primitivo JSON mapeia para o equivalente em TypeScript, e arrays inferem o tipo dos elementos.
// JSON de entrada
{
"id": 42,
"name": "Alice Johnson",
"email": "alice@example.com",
"active": true,
"score": 98.5,
"address": {
"street": "123 Main St",
"city": "New York",
"zip": "10001"
},
"tags": ["admin", "editor"],
"orders": [
{ "id": 101, "total": 49.99, "status": "shipped" },
{ "id": 102, "total": 99.00, "status": "pending" }
]
}// Saída TypeScript
interface Root {
id: number;
name: string;
email: string;
active: boolean;
score: number;
address: Address;
tags: string[];
orders: Order[];
}
interface Address {
street: string;
city: string;
zip: string;
}
interface Order {
id: number;
total: number;
status: string;
}Cada objeto aninhado vira uma interface nomeada. O nome vem da chave pai — address vira Address, itens em orders viram Order. Isso mantém os tipos legíveis e combináveis.
As regras de mapeamento de tipos
Entender como tipos JSON mapeiam para tipos TypeScript torna a saída previsível e permite que você a ajuste com confiança.
| Valor JSON | Tipo TypeScript |
|---|---|
"hello" | string |
42, 3.14 | number |
true, false | boolean |
null | null |
{} | interface nomeada |
['a', 'b'] | string[] |
[1, 'two'] | (number | string)[] |
[] (vazio) | unknown[] |
Uma sutileza: TypeScript não tem tipo inteiro. Tanto 42 quanto 3.14 viram number. Se essa distinção importa para o seu caso, dá para usar branded types ou adicionar um comentário.
Método 1: usar um conversor JSON-para-TypeScript online
Para tarefas pontuais — um novo endpoint de API, um protótipo rápido — uma ferramenta online é o caminho mais rápido. O conversor JSON-para-TypeScript do fixjson faz toda a conversão dentro do seu navegador:
- Cole seu JSON no painel de entrada. A ferramenta aceita JSON válido, ou JSON quebrado com problemas comuns como vírgulas finais e aspas simples — ela conserta o JSON antes de converter.
- As interfaces TypeScript aparecem instantaneamente à direita — sem precisar clicar em nada.
- Copie a saída e cole direto no seu arquivo TypeScript.
Tudo roda localmente. Nenhum dado é enviado a servidor algum — importante quando o JSON que você manipula contém chaves de API, dados de usuário ou configuração interna.
Método 2: escrever a conversão em TypeScript
Quando precisa gerar tipos programaticamente — como parte de um script de build ou gerador de código — dá para escrever a lógica você mesmo. Aqui está uma implementação mínima mas completa:
function jsonToTypeScript(value: unknown, rootName = 'Root'): string {
const interfaces: string[] = [];
const seen = new Set<string>();
function cap(s: string) {
return s ? s.charAt(0).toUpperCase() + s.slice(1) : 'Unknown';
}
function singular(name: string) {
if (/ies$/i.test(name)) return name.slice(0, -3) + 'y';
if (/[^s]s$/i.test(name)) return name.slice(0, -1);
return name + 'Item';
}
function getType(val: unknown, name: string): string {
if (val === null) return 'null';
if (typeof val === 'boolean') return 'boolean';
if (typeof val === 'number') return 'number';
if (typeof val === 'string') return 'string';
if (Array.isArray(val)) {
if (val.length === 0) return 'unknown[]';
const objs = val.filter(
(v): v is Record<string, unknown> =>
v !== null && typeof v === 'object' && !Array.isArray(v),
);
if (objs.length > 0) {
const itemName = cap(singular(name));
buildInterface(itemName, objs);
return `${itemName}[]`;
}
const types = [...new Set(val.map((v) => getType(v, name)))];
return types.length === 1 ? `${types[0]}[]` : `(${types.join(' | ')})[]`;
}
if (typeof val === 'object') {
const iName = cap(name);
buildInterface(iName, [val as Record<string, unknown>]);
return iName;
}
return 'unknown';
}
function buildInterface(name: string, objs: Record<string, unknown>[]): void {
if (seen.has(name)) return;
seen.add(name);
const allKeys = [...new Set(objs.flatMap((o) => Object.keys(o)))];
const props: string[] = [];
for (const key of allKeys) {
const present = objs.filter((o) => key in o);
const optional = present.length < objs.length ? '?' : '';
const propType = getType(present[0][key], cap(key));
const safeKey = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key) ? key : `"${key}"`;
props.push(` ${safeKey}${optional}: ${propType};`);
}
interfaces.push(`interface ${name} {\n${props.join('\n')}\n}`);
}
const rootType = getType(value, rootName);
const defs = interfaces.reverse().join('\n\n');
if (rootType !== cap(rootName)) {
return `type ${cap(rootName)} = ${rootType};\n\n${defs}`.trim();
}
return defs;
}Uso em um script de build Node.js:
import * as fs from 'fs';
const json = JSON.parse(fs.readFileSync('api-response.json', 'utf8'));
const typescript = jsonToTypeScript(json, 'UserResponse');
fs.writeFileSync('types/user.ts', typescript);Método 3: quicktype (CLI e biblioteca)
quicktype é a ferramenta open-source mais completa para gerar tipos TypeScript a partir de JSON. Cobre casos extremos que geradores simples deixam passar e suporta múltiplos formatos de saída.
npm install -g quicktype
# A partir de um arquivo local
quicktype api-response.json -o types/user.ts --lang typescript
# A partir de uma URL
quicktype https://api.example.com/users/42 -o types/user.ts --lang typescript
# A partir de stdin
cat api-response.json | quicktype --lang typescriptquicktype também gera funções de validação em runtime usando bibliotecas como io-ts ou zod — útil quando você precisa validar dados que chegam, não só tipá-los estaticamente.
Lidando com casos complicados
Campos nullable
APIs JSON frequentemente retornam null para valores ausentes. Um gerador que vê null produz null como tipo — mas o que geralmente você quer é string | null:
// Gerado (primeira passagem — restrito demais)
interface Root {
name: string;
middleName: null;
}
// O que você deve escrever
interface Root {
name: string;
middleName: string | null;
}Sempre revise campos nullable manualmente após a geração. Se você tem várias respostas de exemplo em que um campo às vezes é string e às vezes null, um bom gerador inferirá string | null automaticamente.
Opcional vs nullable
TypeScript distingue entre field?: string (a chave pode estar ausente) e field: string | null (a chave sempre está, o valor pode ser null):
interface Order {
id: number;
total: number;
discount?: number; // chave ausente em algumas respostas
note: string | null; // chave sempre presente, valor pode ser null
}Geradores que analisam várias amostras JSON inferem essa distinção com precisão. Geradores de amostra única marcam chaves ausentes como opcionais, mas não conseguem distinguir uma chave faltante de um valor null.
Chaves que não são identificadores válidos
Algumas chaves JSON contêm caracteres que não são nomes válidos de propriedade em TypeScript:
// JSON
{ "content-type": "application/json", "x-request-id": "abc123" }
// TypeScript gerado — válido, mas exige notação de colchetes
interface Root {
"content-type": string;
"x-request-id": string;
}
// Acesso por notação de colchetes
obj["content-type"]Se você controla a API, prefira chaves camelCase para evitar isso totalmente. Se não, a sintaxe de propriedade entre aspas é TypeScript válido e funciona corretamente.
Arrays de tipos mistos
// JSON
{ "values": [1, "two", true, null] }
// Gerado
interface Root {
values: (number | string | boolean | null)[];
}Tipos de união assim são tecnicamente corretos, mas costumam sinalizar um problema de design na API. Se você vê isso, verifique se o campo é realmente misto ou se a amostra está enganando.
Usando os tipos gerados com segurança
Gerar uma interface é apenas o primeiro passo. A interface é um contrato em tempo de compilação — não valida nada em runtime. response.json() retorna any, e o TypeScript deixa você fazer cast para a sua interface sem checagem real.
Para APIs internas em que você controla os dois lados, isso geralmente basta. Para APIs externas ou dados fornecidos pelo usuário, valide em runtime. A abordagem recomendada é Zod:
import { z } from 'zod';
const UserSchema = z.object({
id: z.number(),
name: z.string(),
email: z.string().email(),
active: z.boolean(),
address: z.object({
street: z.string(),
city: z.string(),
zip: z.string(),
}),
tags: z.array(z.string()),
orders: z.array(z.object({
id: z.number(),
total: z.number(),
status: z.string(),
})),
});
type User = z.infer<typeof UserSchema>; // tipo TypeScript derivado do schema
const raw = await response.json();
const user = UserSchema.parse(raw); // lança se a forma não baterA abordagem do Zod é o melhor dos dois mundos: o schema é a única fonte da verdade tanto para o tipo TypeScript quanto para a validação em runtime. Se a API mudar de forma que quebre, você descobre no ponto em que os dados entram no seu sistema.
Além das interfaces: validadores e clientes tipados
Uma interface gerada é um contrato em tempo de compilação. O próximo nível são bibliotecas que dão tanto o tipo quanto a checagem em runtime a partir de uma única fonte:
- Zod e io-ts — defina um schema, derive o tipo TypeScript dele e valide dados na fronteira. Se a API mudar, seu build (e seus logs de erro) avisam na hora.
- tRPC e wrappers de
fetchtipados (por ex.openapi-fetch) — o procedimento do servidor ou a definição OpenAPI é a fonte da verdade, e o cliente recebe tipos compatíveis automaticamente, sem passo JSON-para-TS no meio. - ts-pattern e uniões discriminadas — quando seus dados têm um campo de tag (por ex.
{ kind: "circle" | "square", ... }), pattern matching exaustivo detecta casos esquecidos em tempo de compilação.
Mantendo os tipos sincronizados com a API
Tipos gerados têm prazo de validade. APIs mudam — um campo é renomeado, um novo campo obrigatório aparece, ou um campo string vira nullable. Suas interfaces ficam ultrapassadas silenciosamente.
Gere a partir da spec OpenAPI, não de respostas de exemplo
Se a API tem uma definição OpenAPI (Swagger), openapi-typescript gera tipos diretamente da spec:
npm install -g openapi-typescript
openapi-typescript https://api.example.com/openapi.json -o types/api.tsIsso é muito mais confiável do que gerar a partir de uma resposta de exemplo, porque a spec documenta cada campo, incluindo opcionais, variantes nullable e valores de enum.
Regerar tipos no CI
Adicione um passo no pipeline de CI que regere os tipos a partir da spec ou de uma amostra sabidamente boa e depois rode tsc --noEmit para checar quebras. Se a API mudar, o build falha antes da mudança quebradora chegar à produção.
Perguntas frequentes
Como converto JSON em uma interface TypeScript?
Cole o JSON em um conversor online como a ferramenta JSON-para-TypeScript do fixjson para saída instantânea, rode quicktype em um passo de build, ou gere a partir de uma spec OpenAPI com openapi-typescript para código de longa vida.
Como tipos JSON mapeiam para tipos TypeScript?
Strings → string, todos os números → number (TypeScript não tem tipo inteiro), booleanos → boolean, objetos → interfaces nomeadas, e arrays inferem o tipo dos elementos. Veja a tabela acima e os seis tipos de dados JSON.
Como lido com campos nullable ou opcionais?
Use field?: T quando a chave puder estar ausente e field: T | null quando a chave sempre estiver presente mas o valor puder ser null. Geradores que analisam múltiplas amostras inferem isso; ferramentas de amostra única exigem revisão manual.
Interfaces TypeScript validam JSON em runtime?
Não — interfaces são apagadas em tempo de compilação. Para segurança em runtime com dados externos, valide com Zod ou descreva a forma com JSON Schema.
Conclusão
Converter JSON em interfaces TypeScript é algo que todo desenvolvedor TypeScript faz regularmente. A abordagem certa depende do contexto: para uma integração pontual rápida, um conversor online coloca você em movimento em menos de um minuto. Para um pipeline de build, quicktype ou um script personalizado automatiza o processo. Para código de produção de longa vida, gerar tipos a partir de uma spec OpenAPI e validar em runtime com Zod dá as garantias mais fortes.
Qualquer que seja o método, a interface gerada é um ponto de partida — revise campos nullable, adicione comentários para propriedades não óbvias e aperte as restrições onde suas regras de negócio são mais estritas do que a amostra JSON sugere. Tipos que refletem com precisão seus dados tornam toda a base de código mais fácil de manter.
Se o JSON que você manipula tem erros de sintaxe antes mesmo de tentar converter, conserte-o primeiro. Um gerador TypeScript — como qualquer parser JSON — exige entrada sintaticamente válida.