JSON.parse zwraca any, więc TypeScript akceptuje każdy typ, do którego przypiszesz wynik. Dzięki temu otypowanie sparsowanego JSON-a zajmuje jedną linię, ale oznacza to też, że typ jest twoją obietnicą, a nie czymś, co sprawdza kompilator:
Drugi obiekt ma age jako string "41". TypeScript nadal nazywa to number, bo any da się przypisać do wszystkiego, a program wypisuje 411. Dla JSON-a, który twój kod przed chwilą sam zapisał, adnotacja wystarczy. Dane z żądania, pliku albo local storage trzeba walidować.
Parsowanie do unknown
Otypowanie wyniku jako unknown sprawia, że kompilator wymaga sprawdzenia, zanim użyjesz jakiejkolwiek właściwości:
Błąd to index.ts(4,13): error TS18046: 'data' is of type 'unknown'. Każde kolejne sprawdzenie, które napiszesz, zawęża data trochę bardziej.
Walidacja przez type guard
Type guard to funkcja zwracająca value is User. Gdy zwraca true, TypeScript od tego miejsca traktuje wartość jako User, a sprawdzenia w jej wnętrzu są prawdziwymi sprawdzeniami w czasie działania:
Sam JSON.parse rzuca SyntaxError przy niepoprawnym tekście, więc prawdziwy kod owija go też w try/catch. Przy dużych albo zagnieżdżonych danych ręcznie pisane guardy robią się długie; biblioteki schematów, takie jak Zod czy Valibot, pozwalają zadeklarować kształt raz i wyprowadzić z niego zarówno walidator, jak i typ TypeScript.
Z JSON do interfejsu TypeScript
Zamiana próbki JSON na typy jest mechaniczna. Mając taką odpowiedź:
{
"id": 42,
"title": "Learn TypeScript",
"done": false,
"owner": { "id": 7, "name": "Ada" },
"tags": ["study", "ts"],
"dueDate": "2024-03-15T10:30:00Z",
"notes": null
}
Przypisz każdej wartości jej typ, daj zagnieżdżonym obiektom własny interfejs i oznacz to, co może się zmieniać:
Jedna próbka nie powie, które pola są opcjonalne albo mogą być null. Zanim zdecydujesz o ? i | null, przejrzyj kilka odpowiedzi albo dokumentację API.
Daty i reviver
JSON nie ma typu daty, więc daty przychodzą jako stringi. Drugi argument JSON.parse, czyli reviver, jest wywoływany dla każdego klucza i może je odtworzyć:
Parametr value revivera ma typ any, podobnie jak wynik, więc typ Order nadal opiera się na zaufaniu, a nie na sprawdzeniu. JSON.stringify(order, null, 2) wcina wynik o dwie spacje i zamienia Date z powrotem na string ISO.
JSON.stringify i to, co gubi
JSON.stringify ma typ zwracający string. Wartości, które konwertuje, nie zawsze wracają takie same, a typ o tym nie ostrzega:
| Wartość | Po JSON.stringify |
|---|---|
Date | string ISO (przez metodę toJSON) |
Map, Set | {} (najpierw skonwertuj przez [...set] albo Object.fromEntries(map)) |
undefined, funkcje, symbole w obiekcie | klucz jest pomijany |
undefined, funkcje, symbole w tablicy | null |
samo undefined, funkcja albo symbol | undefined, a nie string |
NaN, Infinity | null |
bigint | rzuca TypeError |
Reguły w czasie działania są takie same jak w zwykłym JavaScripcie; opisuje je strona JSON w JavaScripcie.
Typ dla dowolnej wartości JSON
Gdy kod obsługuje dowolny JSON, typ rekurencyjny opisuje dokładnie to, co JSON może zawierać, i odrzuca wartości, których nie może:
Bez tego komentarza ostatnia linia jest błędem kompilacji, bo obiekt Date nie jest JsonValue.
Importowanie plików .json
Plik .json można zaimportować jak moduł, a TypeScript wnioskuje jego typ z zawartości:
{ "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" };
W TypeScript 7 działa to bez dodatkowych ustawień, gdy module ma wartość nodenext, node20, commonjs, esnext albo preserve. Przy node16 i node18 kończy się błędem TS2732, Cannot find module './config.json'. Consider using '--resolveJsonModule' to import module with '.json' extension., dopóki nie dodasz "resolveJsonModule": true; ustawienie go na false wyłącza importy JSON wszędzie. W ES module przy nodenext albo node20 import wymaga atrybutu with { type: "json" } (bez niego błąd TS1543) i dozwolony jest tylko import domyślny (błąd TS1544 dla import { port }). tsc kopiuje zaimportowany plik .json do outDir obok skompilowanego JavaScriptu.
Najczęściej zadawane pytania
Jaki typ zwraca JSON.parse w TypeScript?
any. Kompilator nie może wiedzieć, co zawiera string, więc const user: User = JSON.parse(text) kompiluje się bez względu na zawartość tekstu. Przypisz wynik do unknown i waliduj go, gdy dane pochodzą spoza twojego programu.
Jak zamienić JSON na interfejs TypeScript?
Weź reprezentatywną próbkę i zapisz jedną właściwość na każdy klucz: stringi, liczby i wartości logiczne odpowiadają typom string, number i boolean, zagnieżdżony obiekt dostaje własny interfejs, tablica obiektów staje się Item[], a klucze, których czasem brakuje, dostają ?. Generatory kodu, takie jak quicktype, robią to automatycznie, ale sprawdź ich domysły na więcej niż jednej próbce.
Jak zaimportować plik JSON w TypeScript?
import config from "./config.json"; działa w TypeScript 7 z module ustawionym na nodenext, node20, commonjs, esnext albo preserve, a wynik jest typowany na podstawie zawartości pliku. Przy node16 albo node18 ustaw dodatkowo "resolveJsonModule": true. W ES module przy nodenext albo node20 dodaj atrybut wymagany przez Node: import config from "./config.json" with { type: "json" };.
Czy JSON.stringify zawsze zwraca string?
Jego typ mówi string, ale JSON.stringify(undefined) i JSON.stringify(() => 1) zwracają w czasie działania undefined. Wartości wewnątrz obiektów też są konwertowane: Date staje się stringiem ISO, a Map i Set stają się {}.