JSON para TypeScript
Gera interfaces TypeScript a partir de JSON: elementos de array mesclados, chaves opcionais, uniões de tipos mistos, null distinto — tudo no navegador.
export interface Root {
id: number
name: string
active: boolean
address: Address
roles: string[]
posts: Post[]
tags: unknown[]
}
export interface Address {
city: string
zip: null
}
export interface Post {
id: number
title: string
views: number | string
pinned?: boolean
}
Interfaces: 3
Tipos inferidos dos dados, não adivinhados
Um documento JSON não carrega os próprios tipos: carrega valores, e os tipos precisam ser lidos de volta a partir deles. Isso é fácil para um único objeto e surpreendentemente sutil para uma coleção: a forma que você quer não é a de nenhum registro específico, mas a forma que todo registro precisa satisfazer. Esta ferramenta infere interfaces TypeScript a partir de uma amostra JSON olhando tudo o que a amostra contém, de modo que os tipos que ela produz descrevem a totalidade dos seus dados e não a primeira linha que por acaso ficou no topo.
A conversão vai em um só sentido. Converter os tipos de volta para JSON exigiria inventar valores, e aqui o objetivo é o oposto: o documento colado é a fonte da verdade, e cada declaração deriva dele. Cole uma resposta de uma API, um arquivo de configuração ou uma linha de log, e leia a interface que você teria escrito à mão de outro modo.
Como os arrays são mesclados
As decisões interessantes acontecem todas nos arrays. Um conversor ingênuo olha o primeiro elemento e para, o que faz todo campo que ele vê por acaso parecer obrigatório e ignora todo campo que ele não vê. Esta ferramenta, em vez disso, mescla todos os elementos em um único tipo, e três coisas decorrem disso:
- Uma chave presente em alguns elementos mas ausente em outros torna-se opcional, escrita com um ponto de interrogação. Se metade dos seus registros tem "middleName" e metade não, o campo é "middleName?", exatamente o que um consumidor precisa tratar.
- Uma chave cujo valor difere em tipo entre os elementos torna-se uma união. Um campo que é um número em um registro e uma string em outro é tipado como "number | string" — não por capricho, mas porque é o que os dados realmente contêm e o que o seu código precisa aceitar.
- Um objeto aninhado dentro dos elementos é mesclado do mesmo jeito, recursivamente, e extraído para a sua própria interface. Dez elementos de um array que carregam cada um um "address" produzem uma única interface Address descrevendo os dez.
Quando o próprio documento é um array no nível superior, a raiz vira um alias para uma interface de item — por exemplo "type Root = RootItem[]" — com o tipo do elemento mesclado escrito abaixo.
Null, opcional e por que são diferentes
É tentador tratar um null igual a uma chave ausente, e é um erro. Em TypeScript "name?: string" significa que a propriedade pode faltar; "name: string | null" significa que ela sempre está lá mas pode conter null. São contratos diferentes, e um consumidor os verifica de forma diferente — "in" contra uma comparação de valor. Esta ferramenta os mantém separados: um null explícito nos dados torna-se um membro de união "| null", colocado por último para que "string | null" se leia como você espera, enquanto uma chave que simplesmente falta em alguns registros torna-se opcional. Um campo que é as duas coisas — null em um registro, ausente em outro — sai como ambas, "field?: T | null", porque os dois fatos são verdadeiros sobre os seus dados.
Objetos aninhados viram interfaces nomeadas
Em vez de embutir uma forma aninhada dentro do pai, cada objeto é extraído para a sua própria interface cujo nome deriva da chave sob a qual ele está. Um objeto "user" vira uma interface User; um "address" dentro dele vira uma interface Address a que User faz referência. Tipos embutidos em profundidade são difíceis de ler e impossíveis de reutilizar, e interfaces nomeadas são o que você mesmo teria escrito. Elementos de um array são postos no singular quando possível — "users" dá um User, "categories" um Category — e uma chave que não pluraliza recebe o sufixo Item para que o elemento tenha um nome próprio.
Se dois objetos diferentes tomassem o mesmo nome — dois "data" sem relação, digamos — o segundo recebe um sufixo em vez de ser mesclado, de modo que formas distintas permaneçam distintas. O objeto raiz é emitido primeiro e você pode renomeá-lo; a escolha entre saída "interface" e "type" é um interruptor, já que algumas bases de código preferem aliases de tipo em toda parte.
O recuo para unknown
Alguns valores não carregam informação de tipo nenhuma. Um array vazio poderia conter qualquer coisa; um objeto vazio não tem chaves a descrever. Em vez de recorrer a "any" — que desliga a verificação de tipos para tudo o que segue — a ferramenta recua para "unknown": um array vazio vira "unknown[]", um objeto vazio vira "Record<string, unknown>". A diferença importa. "any" deixa passar bugs em silêncio; "unknown" obriga o consumidor a estreitar o valor antes de usá-lo, de modo que o tipo inferido continua honesto sobre o que a amostra disse e o que não disse.
São os tipos que você apertaria à mão assim que souber o que a coleção vazia deve conter — mas até os dados dizerem, "unknown" é a resposta honesta, e é a que mantém seguros os demais tipos.
Em que ele roda, e onde
Tudo acontece no seu navegador. O JSON é analisado e os tipos são inferidos no seu próprio dispositivo; nada do que você cola é enviado, armazenado ou registrado. Isso torna a ferramenta segura para usar em uma resposta de API real ou um arquivo de configuração com segredos dentro: a amostra nunca sai da página. A saída é TypeScript comum que você pode colar diretamente em um arquivo "d.ts" ou em um módulo, ajustar os poucos campos "unknown" que os dados não puderam descrever, e usar.
Perguntas frequentes
- Meu JSON é enviado para um servidor?
- Não. O documento é analisado e os tipos são inferidos inteiramente no seu navegador, e nada do que você cola é enviado ou registrado. É seguro usar em uma resposta de API real ou um arquivo de configuração.
- Por que um campo é opcional se está presente na minha amostra?
- Porque ele está ausente de pelo menos um elemento de um array que a ferramenta mesclou. A inferência lê todos os elementos, não apenas o primeiro, então uma chave que alguns registros omitem torna-se opcional — é o tipo que os seus dados realmente admitem, mesmo que o registro que você olhou por acaso a incluísse.
- Por que um campo virou uma união como string | number?
- Porque o valor tinha tipos diferentes em elementos diferentes do array — uma string em um registro e um número em outro. O tipo mesclado precisa aceitar ambos, então é escrito como uma união. Se isso surpreende você, geralmente significa que os dados são menos uniformes do que o esperado, o que vale saber.
- Por que a ferramenta usa unknown em vez de any?
- Para valores que ela não pode descrever — um array vazio, um objeto vazio — "unknown" mantém o resultado seguro em tipos, obrigando um consumidor a estreitar o valor antes de usá-lo, enquanto "any" desligaria a verificação de tipos. Você pode apertar esses campos à mão assim que souber o que a coleção vazia contém.
- Qual é a diferença entre a saída interface e type?
- Nenhuma nos tipos que descrevem — ambas produzem as mesmas formas. "interface" é o idioma comum para tipos de objeto e pode ser estendido e mesclado; aliases "type" são o que algumas bases de código preferem usar em toda parte. O interruptor existe para que a saída combine com o estilo do seu projeto.
- Ele pode converter TypeScript de volta para JSON?
- Não, e de propósito. A conversão é de mão única: JSON entra, tipos saem. Ir na outra direção exigiria inventar valores que nunca estiveram nos seus dados, e todo o objetivo é que cada declaração derive do que você realmente colou.
- Como os objetos aninhados são nomeados?
- Pela chave sob a qual estão: um objeto "user" vira User, um "address" dentro dele vira Address. Elementos de um array são postos no singular quando possível — "categories" dá Category — e uma chave que não pluraliza recebe o sufixo Item. Duas formas diferentes que colidiriam em um nome recebem um sufixo em vez de serem mescladas, então permanecem distintas.