JSON.parse restituisce any, quindi TypeScript accetta qualsiasi tipo a cui assegni il risultato. Questo rende la tipizzazione del JSON letto una questione di una riga, ma significa anche che il tipo è una promessa che fai tu, non qualcosa che il compilatore controlla:
Il secondo oggetto ha age come stringa "41". TypeScript lo chiama comunque number, perché any si può assegnare a qualunque cosa, e il programma stampa 411. Per un JSON che il tuo stesso codice ha scritto poco prima, l'annotazione va bene. Per dati che arrivano da una richiesta, un file o il local storage, validali.
Fare il parse in unknown
Tipizzare il risultato come unknown obbliga il compilatore a pretendere un controllo prima di usare qualsiasi proprietà:
L'errore è index.ts(4,13): error TS18046: 'data' is of type 'unknown'. Ogni controllo che scrivi poi restringe data un po' di più.
Validare con un type guard
Un type guard è una funzione che restituisce value is User. Quando restituisce true, da lì in poi TypeScript tratta il valore come un User, e i controlli al suo interno sono veri controlli a runtime:
JSON.parse stesso lancia un SyntaxError su un testo malformato, quindi il codice reale lo racchiude anche in un try/catch. Per payload grandi o annidati, i guard scritti a mano diventano lunghi; librerie di schemi come Zod o Valibot ti permettono di dichiarare la forma una volta sola e ricavarne sia il validatore sia il tipo TypeScript.
Da JSON a interfaccia TypeScript
Convertire un esempio di JSON in tipi è un lavoro meccanico. Data questa risposta:
{
"id": 42,
"title": "Learn TypeScript",
"done": false,
"owner": { "id": 7, "name": "Ada" },
"tags": ["study", "ts"],
"dueDate": "2024-03-15T10:30:00Z",
"notes": null
}
Associa ogni valore al suo tipo, dai agli oggetti annidati un'interfaccia propria, e segna ciò che può variare:
Un solo esempio non può dirti quali campi sono opzionali o nullable. Guarda diverse risposte, o la documentazione dell'API, prima di decidere su ? e | null.
Date e il reviver
JSON non ha un tipo data, quindi le date arrivano come stringhe. Il secondo argomento di JSON.parse, il reviver, viene chiamato per ogni chiave e può ricostruirle:
Il parametro value del reviver è any, e lo è anche il risultato, quindi del tipo Order ci si fida ancora invece di controllarlo. JSON.stringify(order, null, 2) indenta l'output di due spazi e ritrasforma la Date nella sua stringa ISO.
JSON.stringify e ciò che perde
JSON.stringify è tipizzato per restituire string. I valori che converte non sempre tornano uguali, e il tipo non ti avvisa:
| Valore | Dopo JSON.stringify |
|---|---|
Date | stringa ISO (tramite il suo metodo toJSON) |
Map, Set | {} (prima convertili con [...set] o Object.fromEntries(map)) |
undefined, funzioni, simboli in un oggetto | la chiave viene omessa |
undefined, funzioni, simboli in un array | null |
undefined, una funzione o un simbolo da soli | undefined, non una stringa |
NaN, Infinity | null |
bigint | lancia un TypeError |
Le regole a runtime sono le stesse del JavaScript semplice, spiegate in JSON in JavaScript.
Un tipo per qualsiasi valore JSON
Quando il codice gestisce JSON arbitrario, un tipo ricorsivo descrive esattamente ciò che JSON può contenere e rifiuta i valori che non può contenere:
Senza il commento, l'ultima riga è un errore di compilazione perché un oggetto Date non è un JsonValue.
Importare file .json
Un file .json si può importare come un modulo, e TypeScript ne deduce il tipo dal contenuto:
{ "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" };
In TypeScript 7 questo funziona senza impostazioni aggiuntive con module impostato a nodenext, node20, commonjs, esnext o preserve. Con node16 e node18 fallisce con l'errore TS2732, Cannot find module './config.json'. Consider using '--resolveJsonModule' to import module with '.json' extension., finché non aggiungi "resolveJsonModule": true; impostarlo a false disattiva gli import JSON ovunque. In un ES module con nodenext o node20, l'import richiede l'attributo with { type: "json" } (errore TS1543 senza) ed è permesso solo l'import default (errore TS1544 per import { port }). tsc copia il file .json importato in outDir, accanto al JavaScript compilato.
Domande frequenti
Che tipo restituisce JSON.parse in TypeScript?
any. Il compilatore non può sapere cosa contiene una stringa, quindi const user: User = JSON.parse(text) compila qualunque cosa ci sia nel testo. Assegna il risultato a unknown e validalo quando i dati arrivano da fuori del tuo programma.
Come si converte un JSON in un'interfaccia TypeScript?
Prendi un esempio rappresentativo e scrivi una proprietà per ogni chiave: stringhe, numeri e booleani diventano string, number e boolean, un oggetto annidato diventa un'interfaccia a sé, un array di oggetti diventa Item[], e le chiavi che a volte mancano ricevono ?. Generatori di codice come quicktype lo automatizzano, ma controlla le loro ipotesi su più di un esempio.
Come si importa un file JSON in TypeScript?
import config from "./config.json"; funziona in TypeScript 7 con module impostato a nodenext, node20, commonjs, esnext o preserve, e il risultato è tipizzato in base al contenuto del file. Con node16 o node18, imposta anche "resolveJsonModule": true. In un ES module con nodenext o node20, aggiungi l'attributo richiesto da Node: import config from "./config.json" with { type: "json" };.
JSON.stringify restituisce sempre una stringa?
Il suo tipo dice string, ma JSON.stringify(undefined) e JSON.stringify(() => 1) restituiscono undefined a runtime. Anche i valori dentro gli oggetti vengono convertiti: una Date diventa una stringa ISO, e Map e Set diventano {}.