JSON.parse retorna any, então o TypeScript aceita qualquer tipo ao qual você atribua o resultado. Isso faz com que tipar JSON lido seja uma linha só, mas também significa que o tipo é uma promessa sua, não algo que o compilador verifica:
O segundo objeto tem age como a string "41". O TypeScript continua chamando isso de number, porque any pode ser atribuído a qualquer coisa, e o programa imprime 411. Para um JSON que o seu próprio código acabou de escrever, a anotação basta. Para dados de uma requisição, de um arquivo ou do local storage, valide.
Fazendo parse para unknown
Tipar o resultado como unknown faz o compilador exigir uma verificação antes de qualquer propriedade ser usada:
O erro é index.ts(4,13): error TS18046: 'data' is of type 'unknown'. Cada verificação que você escreve depois estreita data um pouco mais.
Validando com um type guard
Um type guard é uma função que retorna value is User. Quando ela retorna true, o TypeScript passa a tratar o valor como User, e as verificações dentro dela são verificações reais em tempo de execução:
O próprio JSON.parse lança um SyntaxError para texto malformado, então código real também o envolve em try/catch. Para payloads grandes ou aninhados, type guards escritos à mão ficam longos; bibliotecas de schema como Zod ou Valibot permitem declarar o formato uma vez e derivar dele tanto o validador quanto o tipo TypeScript.
JSON para interface TypeScript
Converter um exemplo de JSON em tipos é mecânico. Dada esta resposta:
{
"id": 42,
"title": "Learn TypeScript",
"done": false,
"owner": { "id": 7, "name": "Ada" },
"tags": ["study", "ts"],
"dueDate": "2024-03-15T10:30:00Z",
"notes": null
}
Associe cada valor ao seu tipo, dê uma interface própria aos objetos aninhados e marque o que pode variar:
Um único exemplo não diz quais campos são opcionais ou podem ser null. Olhe várias respostas, ou a documentação da API, antes de decidir onde vão ? e | null.
Datas e o reviver
JSON não tem tipo de data, então as datas chegam como strings. O segundo argumento de JSON.parse, o reviver, é chamado para cada chave e pode reconstruí-las:
O parâmetro value do reviver é any, e o resultado também, então o tipo Order continua sendo confiado, e não verificado. JSON.stringify(order, null, 2) indenta a saída com dois espaços e transforma o Date de volta na sua string ISO.
JSON.stringify e o que ele perde
JSON.stringify é tipado para retornar string. Os valores que ele converte nem sempre voltam iguais, e o tipo não avisa:
| Valor | Depois de JSON.stringify |
|---|---|
Date | string ISO (pelo método toJSON) |
Map, Set | {} (converta antes com [...set] ou Object.fromEntries(map)) |
undefined, funções e symbols dentro de um objeto | a chave é omitida |
undefined, funções e symbols dentro de um array | null |
undefined, uma função ou um symbol sozinhos | undefined, não uma string |
NaN, Infinity | null |
bigint | lança um TypeError |
As regras em tempo de execução são as mesmas do JavaScript puro, explicadas em JSON no JavaScript.
Um tipo para qualquer valor JSON
Quando o código lida com JSON arbitrário, um tipo recursivo descreve exatamente o que o JSON pode conter e rejeita o que ele não pode:
Sem o comentário, a última linha é um erro de compilação, porque um objeto Date não é um JsonValue.
Importando arquivos .json
Um arquivo .json pode ser importado como um módulo, e o TypeScript infere o tipo a partir do conteúdo:
{ "name": "app", "port": 8080, "tags": ["a"] }
// CommonJS output, or a bundler
import config from "./config.json";
const port: number = config.port; // typed from the file: number
// An ES module under module: nodenext
import settings from "./config.json" with { type: "json" };
No TypeScript 7 isso funciona sem configurações extras com module definido como nodenext, node20, commonjs, esnext ou preserve. Com node16 e node18 falha com o erro TS2732, Cannot find module './config.json'. Consider using '--resolveJsonModule' to import module with '.json' extension., até você adicionar "resolveJsonModule": true; defini-la como false desativa imports de JSON em qualquer caso. Em um ES module com nodenext ou node20, o import precisa do atributo with { type: "json" } (erro TS1543 sem ele) e só o import default é permitido (erro TS1544 para import { port }). O tsc copia o arquivo .json importado para o outDir, ao lado do JavaScript compilado.
Perguntas frequentes
Que tipo JSON.parse retorna no TypeScript?
any. O compilador não tem como saber o que uma string contém, então const user: User = JSON.parse(text) compila seja qual for o conteúdo do texto. Atribua o resultado a unknown e valide quando os dados vierem de fora do seu programa.
Como converter JSON em uma interface TypeScript?
Pegue um exemplo representativo e escreva uma propriedade por chave: strings, números e booleanos viram string, number e boolean, um objeto aninhado vira uma interface própria, um array de objetos vira Item[] e chaves que às vezes faltam recebem ?. Geradores de código como o quicktype automatizam isso, mas confira os palpites deles com mais de um exemplo.
Como importar um arquivo JSON no TypeScript?
import config from "./config.json"; funciona no TypeScript 7 com module definido como nodenext, node20, commonjs, esnext ou preserve, e o resultado é tipado a partir do conteúdo do arquivo. Com node16 ou node18, defina também "resolveJsonModule": true. Em um ES module com nodenext ou node20, adicione o atributo que o Node exige: import config from "./config.json" with { type: "json" };.
JSON.stringify sempre retorna uma string?
O tipo dele diz string, mas JSON.stringify(undefined) e JSON.stringify(() => 1) retornam undefined em tempo de execução. Os valores dentro de objetos também são convertidos: um Date vira uma string ISO, e Map e Set viram {}.