← Tous les articles

Comment générer des interfaces TypeScript depuis du JSON

Apprenez à convertir du JSON en interfaces TypeScript — à la main, avec des outils en ligne et en code. Couvre les objets imbriqués, les champs optionnels, les tableaux, les types nullables et la synchronisation des types avec votre API.

Chaque développeur TypeScript a connu cette situation : vous intégrez une nouvelle API, vous avez un exemple de réponse, et vous avez besoin d'interfaces typées avant de pouvoir écrire la moindre ligne de logique métier. Écrire ces interfaces à la main est fastidieux, répétitif et propice aux erreurs — surtout quand la réponse est très imbriquée ou comporte des dizaines de champs. Ce guide couvre toutes les approches pratiques pour convertir du JSON en interfaces TypeScript : à quoi doit ressembler la sortie, comment la générer automatiquement et comment gérer les cas délicats.

Pourquoi les interfaces TypeScript comptent pour les données JSON

Quand vous appelez une API en TypeScript sans annotations de type, la réponse parsée est any. Cela signifie : pas d'auto-complétion, pas de vérification de type et pas de sûreté à la compilation. Une faute de frappe dans un nom de propriété devient un bug d'exécution au lieu d'une erreur de build.

// Sans types — tout est any
const response = await fetch('/api/users/42');
const user = await response.json(); // user: any
console.log(user.naem); // faute de frappe — pas d'erreur à la compilation

// Avec une interface typée
interface User {
  id: number;
  name: string;
  email: string;
}

const response = await fetch('/api/users/42');
const user: User = await response.json();
console.log(user.naem); // Error: Property 'naem' does not exist on type 'User'

L'interface ajoute zéro coût d'exécution — elle n'existe qu'à la compilation. Mais elle transforme totalement l'expérience de développement.

Ce que produit une conversion JSON-vers-TypeScript

Chaque objet JSON devient une interface TypeScript, chaque primitive JSON correspond à son équivalent TypeScript, et les tableaux infèrent le type de leurs éléments.

// JSON d'entrée
{
  "id": 42,
  "name": "Alice Johnson",
  "email": "alice@example.com",
  "active": true,
  "score": 98.5,
  "address": {
    "street": "123 Main St",
    "city": "New York",
    "zip": "10001"
  },
  "tags": ["admin", "editor"],
  "orders": [
    { "id": 101, "total": 49.99, "status": "shipped" },
    { "id": 102, "total": 99.00, "status": "pending" }
  ]
}
// Sortie TypeScript
interface Root {
  id: number;
  name: string;
  email: string;
  active: boolean;
  score: number;
  address: Address;
  tags: string[];
  orders: Order[];
}

interface Address {
  street: string;
  city: string;
  zip: string;
}

interface Order {
  id: number;
  total: number;
  status: string;
}

Chaque objet imbriqué devient une interface nommée. Le nom vient de la clé parente — address devient Address, les éléments de orders deviennent Order. Cela garde les types lisibles et composables.

Les règles de correspondance des types

Comprendre comment les types JSON correspondent aux types TypeScript rend la sortie prévisible et permet de l'ajuster en confiance.

Valeur JSONType TypeScript
"hello"string
42, 3.14number
true, falseboolean
nullnull
{}interface nommée
['a', 'b']string[]
[1, 'two'](number | string)[]
[] (vide)unknown[]

Une nuance : TypeScript n'a pas de type entier. 42 comme 3.14 deviennent number. Si cette distinction compte pour votre cas, vous pouvez utiliser des types brandés ou ajouter un commentaire.

Méthode 1 : utiliser un convertisseur JSON-vers-TypeScript en ligne

Pour les tâches ponctuelles — un nouvel endpoint d'API, un prototype rapide — un outil en ligne est la voie la plus rapide. Le convertisseur JSON-vers-TypeScript de fixjson effectue toute la conversion dans votre navigateur :

  1. Collez votre JSON dans le panneau d'entrée. L'outil accepte le JSON valide ou le JSON cassé avec des problèmes courants comme les virgules finales et les guillemets simples — il répare le JSON avant la conversion.
  2. Les interfaces TypeScript apparaissent instantanément à droite — aucun bouton à cliquer.
  3. Copiez la sortie et collez-la directement dans votre fichier TypeScript.

Tout s'exécute localement. Aucune donnée n'est envoyée à un serveur — important quand le JSON que vous manipulez contient des clés d'API, des données utilisateur ou de la configuration interne.

Méthode 2 : écrire la conversion en TypeScript

Quand vous devez générer des types par programmation — dans le cadre d'un script de build ou d'un générateur de code — vous pouvez écrire la logique vous-même. Voici une implémentation minimale mais complète :

function jsonToTypeScript(value: unknown, rootName = 'Root'): string {
  const interfaces: string[] = [];
  const seen = new Set<string>();

  function cap(s: string) {
    return s ? s.charAt(0).toUpperCase() + s.slice(1) : 'Unknown';
  }

  function singular(name: string) {
    if (/ies$/i.test(name)) return name.slice(0, -3) + 'y';
    if (/[^s]s$/i.test(name)) return name.slice(0, -1);
    return name + 'Item';
  }

  function getType(val: unknown, name: string): string {
    if (val === null) return 'null';
    if (typeof val === 'boolean') return 'boolean';
    if (typeof val === 'number') return 'number';
    if (typeof val === 'string') return 'string';
    if (Array.isArray(val)) {
      if (val.length === 0) return 'unknown[]';
      const objs = val.filter(
        (v): v is Record<string, unknown> =>
          v !== null && typeof v === 'object' && !Array.isArray(v),
      );
      if (objs.length > 0) {
        const itemName = cap(singular(name));
        buildInterface(itemName, objs);
        return `${itemName}[]`;
      }
      const types = [...new Set(val.map((v) => getType(v, name)))];
      return types.length === 1 ? `${types[0]}[]` : `(${types.join(' | ')})[]`;
    }
    if (typeof val === 'object') {
      const iName = cap(name);
      buildInterface(iName, [val as Record<string, unknown>]);
      return iName;
    }
    return 'unknown';
  }

  function buildInterface(name: string, objs: Record<string, unknown>[]): void {
    if (seen.has(name)) return;
    seen.add(name);
    const allKeys = [...new Set(objs.flatMap((o) => Object.keys(o)))];
    const props: string[] = [];
    for (const key of allKeys) {
      const present = objs.filter((o) => key in o);
      const optional = present.length < objs.length ? '?' : '';
      const propType = getType(present[0][key], cap(key));
      const safeKey = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key) ? key : `"${key}"`;
      props.push(`  ${safeKey}${optional}: ${propType};`);
    }
    interfaces.push(`interface ${name} {\n${props.join('\n')}\n}`);
  }

  const rootType = getType(value, rootName);
  const defs = interfaces.reverse().join('\n\n');
  if (rootType !== cap(rootName)) {
    return `type ${cap(rootName)} = ${rootType};\n\n${defs}`.trim();
  }
  return defs;
}

Utilisation dans un script de build Node.js :

import * as fs from 'fs';

const json = JSON.parse(fs.readFileSync('api-response.json', 'utf8'));
const typescript = jsonToTypeScript(json, 'UserResponse');
fs.writeFileSync('types/user.ts', typescript);

Méthode 3 : quicktype (CLI et bibliothèque)

quicktype est l'outil open source le plus complet pour générer des types TypeScript à partir de JSON. Il gère les cas limites que les générateurs simples ratent et prend en charge plusieurs formats de sortie.

npm install -g quicktype

# Depuis un fichier local
quicktype api-response.json -o types/user.ts --lang typescript

# Depuis une URL
quicktype https://api.example.com/users/42 -o types/user.ts --lang typescript

# Depuis stdin
cat api-response.json | quicktype --lang typescript

quicktype génère aussi des fonctions de validation à l'exécution avec des bibliothèques comme io-ts ou zod — utile quand vous devez valider les données entrantes, pas seulement les typer statiquement.

Gérer les cas délicats

Champs nullables

Les API JSON renvoient fréquemment null pour les valeurs manquantes. Un générateur voyant null produit null comme type — mais ce que vous voulez en général c'est string | null :

// Généré (premier jet — trop restrictif)
interface Root {
  name: string;
  middleName: null;
}

// Ce que vous devriez écrire
interface Root {
  name: string;
  middleName: string | null;
}

Relisez toujours les champs nullables à la main après la génération. Si vous avez plusieurs échantillons de réponse où un champ est parfois une chaîne et parfois null, un bon générateur inférera automatiquement string | null.

Optionnel vs nullable

TypeScript distingue field?: string (la clé peut être absente) et field: string | null (la clé est toujours présente, sa valeur peut être null) :

interface Order {
  id: number;
  total: number;
  discount?: number;   // clé absente dans certaines réponses
  note: string | null; // clé toujours présente, valeur peut être null
}

Les générateurs qui analysent plusieurs échantillons JSON peuvent inférer cette distinction avec précision. Les générateurs mono-échantillon marquent les clés absentes comme optionnelles mais ne peuvent pas distinguer une clé manquante d'une valeur null.

Clés qui ne sont pas des identifiants valides

Certaines clés JSON contiennent des caractères qui ne sont pas des noms de propriété TypeScript valides :

// JSON
{ "content-type": "application/json", "x-request-id": "abc123" }

// TypeScript généré — valide mais nécessite la notation crochets
interface Root {
  "content-type": string;
  "x-request-id": string;
}

// Accès avec notation crochets
obj["content-type"]

Si vous contrôlez l'API, préférez des clés en camelCase pour éviter ce problème entièrement. Sinon, la syntaxe de propriété entre guillemets est du TypeScript valide et fonctionne correctement.

Tableaux de types mixtes

// JSON
{ "values": [1, "two", true, null] }

// Généré
interface Root {
  values: (number | string | boolean | null)[];
}

Ces types union sont techniquement corrects mais trahissent en général un problème de conception de l'API. Si vous voyez cela, vérifiez si le champ est réellement mixte ou si l'échantillon est trompeur.

Utiliser les types générés en toute sécurité

Générer une interface n'est que la première étape. L'interface est un contrat à la compilation — elle ne valide rien à l'exécution. response.json() renvoie any, et TypeScript vous laissera la caster vers votre interface sans vraie vérification.

Pour les API internes où vous contrôlez les deux côtés, c'est généralement suffisant. Pour les API externes ou les données fournies par l'utilisateur, validez à l'exécution. L'approche recommandée est Zod :

import { z } from 'zod';

const UserSchema = z.object({
  id: z.number(),
  name: z.string(),
  email: z.string().email(),
  active: z.boolean(),
  address: z.object({
    street: z.string(),
    city: z.string(),
    zip: z.string(),
  }),
  tags: z.array(z.string()),
  orders: z.array(z.object({
    id: z.number(),
    total: z.number(),
    status: z.string(),
  })),
});

type User = z.infer<typeof UserSchema>; // type TypeScript dérivé du schéma

const raw = await response.json();
const user = UserSchema.parse(raw); // lance une erreur si la forme ne correspond pas

L'approche de Zod cumule les avantages : le schéma est la source unique de vérité à la fois pour le type TypeScript et la validation à l'exécution. Si l'API change de manière incompatible, vous le découvrez au point où les données entrent dans votre système.

Au-delà des interfaces : validateurs et clients typés

Une interface générée est un contrat à la compilation. Le niveau supérieur, ce sont des bibliothèques qui vous donnent à la fois le type et la vérification à l'exécution depuis une seule source :

  • Zod et io-ts — définissez un schéma, dérivez-en le type TypeScript et validez les données entrantes à la frontière. Si l'API change, votre build (et vos logs d'erreur) vous le disent immédiatement.
  • tRPC et wrappers fetch typés (par ex. openapi-fetch) — la procédure serveur ou la définition OpenAPI est la source de vérité, et le client récupère automatiquement les types correspondants, sans étape JSON-vers-TS au milieu.
  • ts-pattern et unions discriminées — dès que vos données ont un champ tag (par ex. { kind: "circle" | "square", ... }), le pattern matching exhaustif détecte les cas oubliés à la compilation.

Garder les types synchronisés avec l'API

Les types générés ont une durée de vie limitée. Les API changent — un champ est renommé, un nouveau champ requis apparaît, ou un champ string devient nullable. Vos interfaces deviennent obsolètes en silence.

Générer depuis la spec OpenAPI, pas depuis des réponses d'exemple

Si l'API a une définition OpenAPI (Swagger), openapi-typescript génère les types directement depuis la spec :

npm install -g openapi-typescript
openapi-typescript https://api.example.com/openapi.json -o types/api.ts

C'est bien plus fiable que de générer depuis un exemple de réponse, parce que la spec documente chaque champ, y compris les optionnels, les variantes nullables et les valeurs d'énumération.

Régénérer les types en CI

Ajoutez une étape à votre pipeline CI qui régénère les types depuis la spec ou un échantillon connu valide, puis exécute tsc --noEmit pour vérifier les cassures. Si l'API change, le build échoue avant que le changement cassant n'atteigne la production.

Foire aux questions

Comment convertir du JSON en interface TypeScript ?

Collez le JSON dans un convertisseur en ligne comme l'outil JSON-vers-TypeScript de fixjson pour une sortie instantanée, exécutez quicktype dans une étape de build, ou générez depuis une spec OpenAPI avec openapi-typescript pour du code durable.

Comment les types JSON correspondent-ils aux types TypeScript ?

Chaînes → string, tous les nombres → number (TypeScript n'a pas de type entier), booléens → boolean, objets → interfaces nommées, et les tableaux infèrent leur type d'élément. Voir le tableau de correspondance ci-dessus et les six types de données JSON.

Comment gérer les champs nullables ou optionnels ?

Utilisez field?: T quand la clé peut être absente et field: T | null quand la clé est toujours présente mais la valeur peut être null. Les générateurs qui analysent plusieurs échantillons infèrent cela ; les outils mono-échantillon nécessitent une revue manuelle.

Les interfaces TypeScript valident-elles le JSON à l'exécution ?

Non — les interfaces sont effacées à la compilation. Pour la sûreté à l'exécution sur des données externes, validez avec Zod ou décrivez la forme avec JSON Schema.

Conclusion

Convertir du JSON en interfaces TypeScript est une tâche que tout développeur TypeScript réalise régulièrement. La bonne approche dépend du contexte : pour une intégration ponctuelle rapide, un convertisseur en ligne vous met en route en moins d'une minute. Pour un pipeline de build, quicktype ou un script maison automatise le processus. Pour du code de production durable, générer les types depuis une spec OpenAPI et valider à l'exécution avec Zod offre les garanties les plus solides.

Quelle que soit la méthode, l'interface générée est un point de départ — relisez les champs nullables, ajoutez des commentaires pour les propriétés non évidentes, et durcissez les contraintes là où vos règles métier sont plus strictes que ce que l'échantillon JSON laisse entendre. Des types qui reflètent fidèlement vos données rendent l'ensemble de la codebase plus facile à maintenir.

Si le JSON que vous manipulez a des erreurs de syntaxe avant même d'essayer de le convertir, réparez-le d'abord. Un générateur TypeScript — comme tout parseur JSON — exige une entrée syntaxiquement valide.