Der Konverter erstellt einen Entwurf aus den Beispielwerten. Die folgenden manuellen Anpassungen erfordern Kenntnis des API-Vertrags; abgeleitete Typen prüfen keine Daten zur Laufzeit.
Beginnen wir mit den Grundlagen. Wie verwandeln Sie ein gegebenes JSON-Objekt in ein TypeScript-Interface?
Hier ist eine einfache Benutzerantwort von einer API:
{
"id": 42,
"username": "jdoe",
"email": "[email protected]",
"isActive": true
}
Das entsprechende TypeScript-Interface:
interface User {
id: number;
username: string;
email: string;
isActive: boolean;
}
Jeder JSON-Wert wird einem TypeScript-Primitive zugeordnet: number, string oder boolean. Objektschlüssel werden zu Eigenschaftsnamen.
Echte APIs geben selten flache Strukturen zurück. Betrachten Sie einen Benutzer mit einer Adresse:
{
"id": 42,
"username": "jdoe",
"address": {
"street": "123 Main St",
"city": "Springfield",
"zipCode": "62704",
"country": "US"
}
}
Teilen Sie verschachtelte Objekte in eigene Interfaces auf:
interface Address {
street: string;
city: string;
zipCode: string;
country: string;
}
interface User {
id: number;
username: string;
address: Address;
}
Das hält jeden Typ fokussiert und wiederverwendbar. Wenn ein anderer Endpunkt ebenfalls eine Address zurückgibt, verwenden Sie dasselbe Interface.
Wenn ein Feld ein Array enthält, annotieren Sie den Elementtyp:
{
"id": 42,
"username": "jdoe",
"roles": ["admin", "editor"],
"posts": [
{ "id": 1, "title": "Hello World" },
{ "id": 2, "title": "TypeScript Tips" }
]
}
interface Post {
id: number;
title: string;
}
interface User {
id: number;
username: string;
roles: string[];
posts: Post[];
}
Verwenden Sie die Type[]-Syntax für einfache Fälle. Für komplexe Generics kann Array<Type> klarer sein, aber Konsistenz ist wichtiger als die Wahl der Variante.
APIs geben oft Felder zurück, die vorhanden sein können oder nicht. Markieren Sie diese mit ?:
{
"id": 42,
"username": "jdoe",
"bio": "Software developer"
}
interface User {
id: number;
username: string;
bio?: string; // Kann in der Antwort fehlen
}
Ein optionales Feld (bio?: string) bedeutet, dass der Schlüssel komplett fehlen könnte. Das unterscheidet sich von einem nullbaren Feld, bei dem der Schlüssel existiert, der Wert aber null sein könnte.
Produktions-APIs sind unordentlich. So gehen Sie mit den häufigsten Stolperfallen um.
Wenn ein Feld explizit null sein kann:
{
"id": 42,
"username": "jdoe",
"avatarUrl": null,
"deletedAt": null
}
interface User {
id: number;
username: string;
avatarUrl: string | null;
deletedAt: string | null; // ISO-Datumsstring oder null
}
Wenn ein Feld sowohl fehlen als auch null sein kann, kombinieren Sie beides:
interface User {
id: number;
username: string;
middleName?: string | null; // Kann fehlen oder vorhanden aber null sein
}
Manche Felder akzeptieren mehrere Typen. Eine Such-API könnte gemischte Ergebnisse zurückgeben:
{
"results": [
{ "type": "user", "id": 1, "username": "jdoe" },
{ "type": "post", "id": 2, "title": "Hello" }
]
}
Modellieren Sie dies mit einer diskriminierten Union:
interface UserResult {
type: 'user';
id: number;
username: string;
}
interface PostResult {
type: 'post';
id: number;
title: string;
}
type SearchResult = UserResult | PostResult;
interface SearchResponse {
results: SearchResult[];
}
Das type-Feld fungiert als Diskriminator. TypeScript verengt den Typ automatisch, wenn Sie ihn prüfen:
function renderResult(result: SearchResult) {
if (result.type === 'user') {
console.log(result.username); // TypeScript weiß, dass dies UserResult ist
} else {
console.log(result.title); // TypeScript weiß, dass dies PostResult ist
}
}
Wenn eine API eine feste Menge von String-Werten zurückgibt, verwenden Sie String-Literal-Unions anstelle von TypeScript-Enums:
{ "status": "active" }
type UserStatus = 'active' | 'inactive' | 'suspended';
interface User {
id: number;
status: UserStatus;
}
String-Literal-Unions sind einfacher, Tree-Shaking-fähig und erzeugen kein Laufzeit-JavaScript wie enum. Reservieren Sie enum für Fälle, in denen Sie Reverse-Mapping (Zahl zu Name) benötigen.
Sowohl interface als auch type können Objektformen beschreiben. So entscheiden Sie, wann Sie was verwenden:
Verwenden Sie interface für Objektformen, die Sie möglicherweise erweitern:
interface BaseEntity {
id: number;
createdAt: string;
updatedAt: string;
}
interface User extends BaseEntity {
username: string;
email: string;
}
Verwenden Sie type für Unions, Intersections und berechnete Typen:
type ApiResponse<T> = {
data: T;
meta: { total: number; page: number };
};
type UserOrAdmin = User | Admin;
In der Praxis funktioniert beides für die meisten Fälle. Wählen Sie eine Konvention für Ihr Projekt und bleiben Sie dabei. Viele Teams verwenden interface für Datenmodelle und type für alles andere.
Für maximale Sicherheit definieren Sie Ihre Formen mit Zod und leiten TypeScript-Typen daraus ab:
import { z } from 'zod';
const UserSchema = z.object({
id: z.number(),
username: z.string(),
email: z.string().email(),
isActive: z.boolean(),
bio: z.string().nullable().optional(),
});
type User = z.infer<typeof UserSchema>;
// Jetzt zur Laufzeit validieren
const response = await fetch('/api/users/42');
const data = await response.json();
const user = UserSchema.parse(data); // Wirft einen Fehler, wenn die Form nicht passt
Mit Zod leben Ihre Typen und Validierungslogik an einem Ort. Wenn die API ihre Form ändert, wirft parse() sofort einen Fehler, anstatt fehlerhafte Daten durch Ihre App propagieren zu lassen.
Typen sind nur nützlich, wenn Sie sie an der Grenzschicht anwenden, wo Daten in Ihre Anwendung eintreten.
Erstellen Sie eine generische Fetch-Funktion, die Typen erzwingt:
async function fetchJson<T>(url: string, schema?: z.ZodType<T>): Promise<T> {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
if (schema) {
return schema.parse(data);
}
return data as T;
}
// Verwendung
const user = await fetchJson<User>('/api/users/42');
const validatedUser = await fetchJson('/api/users/42', UserSchema);
Wenn Sie Axios verwenden, wenden Sie Typen auf den Response-Generic an:
import axios from 'axios';
interface PaginatedResponse<T> {
data: T[];
meta: {
total: number;
page: number;
perPage: number;
lastPage: number;
};
}
async function getUsers(page: number = 1) {
const response = await axios.get<PaginatedResponse<User>>('/api/users', {
params: { page },
});
return response.data; // Vollständig typisiert
}
In React-Apps kombinieren Sie Typen mit Ihrer Datenabruf-Bibliothek:
import { useQuery } from '@tanstack/react-query';
function useUser(id: number) {
return useQuery({
queryKey: ['user', id],
queryFn: () => fetchJson<User>(`/api/users/${id}`),
});
}
// In Ihrer Komponente
const { data: user, isLoading } = useUser(42);
// user ist typisiert als User | undefined
Aktivieren Sie in Ihrer tsconfig.json den strict-Modus (oder mindestens strictNullChecks). Dies zwingt Sie, null und undefined explizit zu behandeln:
{
"compilerOptions": {
"strict": true
}
}
Ohne strikte Null-Prüfungen lässt TypeScript Sie auf .username bei einem potenziell null-Wert zugreifen, ohne sich zu beschweren. Das macht den ganzen Zweck der Typisierung Ihrer API-Antworten zunichte.
readonly für unveränderliche Daten verwendenAPI-Antworten sind Daten, die Sie erhalten haben, nicht Daten, die Sie verändern sollten. Markieren Sie Eigenschaften als readonly:
interface User {
readonly id: number;
readonly username: string;
readonly email: string;
readonly createdAt: string;
}
Oder verwenden Sie einen Utility-Typ für das gesamte Interface:
type ImmutableUser = Readonly<User>;
Dies verhindert versehentliche Mutationen, die subtile Fehler verursachen.
Verhindern Sie das Verwechseln von IDs verschiedener Entitäten mit Branded Types:
type UserId = number & { readonly __brand: 'UserId' };
type PostId = number & { readonly __brand: 'PostId' };
function getUser(id: UserId): Promise<User> { /* ... */ }
function getPost(id: PostId): Promise<Post> { /* ... */ }
const userId = 42 as UserId;
const postId = 7 as PostId;
getUser(userId); // OK
getUser(postId); // Typfehler! PostId kann nicht übergeben werden, wo UserId erwartet wird
Bewahren Sie alle API-bezogenen Typen in einem dedizierten Verzeichnis auf:
src/
types/
api/
user.ts
post.ts
common.ts // Gemeinsame Typen wie PaginatedResponse
index.ts // Re-Exports
Das macht Typen leicht auffindbar, importierbar und wartbar. Wenn sich eine API ändert, aktualisieren Sie eine Datei, und der Compiler zeigt Ihnen alles, was bricht.
Vertrauen Sie niemals externen Daten. Selbst mit perfekten Typen könnte die API etwas Unerwartetes zurückgeben. Validieren Sie Antworten dort, wo sie in Ihre App eintreten:
// Nicht so
const user = (await response.json()) as User; // Vertraut blind
// Sondern so
const data = await response.json();
const user = UserSchema.parse(data); // Validiert die Struktur
Typ-Assertions (as User) bringen den Compiler zum Schweigen, prüfen aber nicht die Daten. Laufzeitvalidierung (Zod, io-ts, Valibot) verifiziert tatsächlich, ob die Struktur übereinstimmt.
Wenn Sie mit großen oder komplexen JSON-Payloads arbeiten, verwenden Sie den JSON-Editor, um die Struktur zu erkunden und zu verstehen, bevor Sie Typen schreiben. Es ist viel einfacher, verschachtelte Beziehungen in einem richtigen Editor zu erkennen als in Rohtext.
Für Teams, die mit JSON-APIs arbeiten, schauen Sie sich diese verwandten Leitfäden an:
Wenn Ihre API eine OpenAPI-Spezifikation (Swagger) hat, überspringen Sie die JSON-Konvertierung komplett. Tools wie openapi-typescript generieren Typen direkt aus der Spezifikation:
npx openapi-typescript https://api.example.com/openapi.json -o ./src/types/api.ts
Dieser Ansatz ist überlegen, da die Spezifikation optionale Felder, nullbare Typen und Enums explizit definiert. Sie erhalten genaue Typen, ohne aus Beispieldaten raten zu müssen.
Die Verarbeitung erfolgt im Browser. Speichern und Teilen sind optional, erfordern ein Konto und übertragen den gewählten Inhalt an unseren Dienst.
Ja. Der Konverter generiert separate Interfaces für jedes verschachtelte Objekt und referenziert sie korrekt.
Die generierten TypeScript-Typen sind produktionsreif. Kopieren Sie sie oder laden Sie sie als .ts-Datei herunter.
Ja. Der Konverter erkennt Array-Typen und generiert die korrekte TypeScript-Array-Syntax (Type[]). Optionale Felder werden erkannt, wenn Werte null oder undefined sein können.