
Il y a trois facons de transformer une reponse JSON en interfaces TypeScript. On peut les ecrire a la main, demander a un LLM, ou passer le JSON dans un convertisseur deterministe. J'ai utilise les trois. Deux d'entre elles ont des modes de defaillance auxquels la plupart des gens ne pensent pas jusqu'a ce qu'ils deployent un bug.
L'approche manuelle : lente et precise jusqu'a ce qu'elle ne le soit plus
Ecrire des interfaces a la main fonctionne quand on a trois champs. Ca s'arrete vers le quinzieme. Un objet charge Stripe a plus de 40 proprietes. Une reponse de pull request GitHub depasse les 100 champs une fois qu'on compte les objets imbriques. Personne ne les type a la main sans faire d'erreurs.
Le mode de defaillance est subtil. On ouvre la doc API, on commence a ecrire, et au vingtieme champ on survole. merged_at etait un string ou un Date ? labels est un tableau d'objets ou de strings ? On devine, on continue, et le compilateur TypeScript fait confiance a ce qu'on a ecrit. Le systeme de types ne detecte les erreurs que si les types sont corrects au depart.
La documentation TypeScript le dit clairement : any desactive la verification de types pour cette valeur. Mais une interface incorrecte est sans doute pire que any, parce qu'elle donne une fausse confiance. L'IDE autocomplete des champs qui n'existent pas. Le code compile. Le crash arrive au runtime.
L'approche LLM : rapide et probabiliste
Coller un bloc JSON dans ChatGPT ou Claude et demander des interfaces TypeScript est tentant. C'est rapide. Ca gere l'imbrication. Ca nomme meme les interfaces de maniere raisonnable la plupart du temps.
Le probleme est que les LLMs sont probabilistes. Donne le meme JSON au meme modele deux fois et tu pourrais obtenir des sorties differentes. Parfois il ajoute ? a des champs qui ne sont pas optionnels. Parfois il invente un type union qui ne correspond pas aux donnees. Parfois il decide que id devrait etre string alors que la valeur est clairement 1. J'ai vu des modeles produire Date pour des strings de timestamp ISO ; techniquement aspirationnel, mais incorrect si on ne parse pas le string en objet Date d'abord.
Ce ne sont pas des bugs du modele. C'est la nature de l'outil. Un LLM genere du texte plausible base sur des patterns. Il ne parse pas ton JSON comme le fait un systeme de types. Il le lit, approxime ce que les types devraient etre, et ecrit quelque chose qui a l'air correct. La plupart du temps ca l'est. Mais "correct la plupart du temps" et "deterministiquement correct" sont des choses differentes quand tes definitions de types protegent le comportement au runtime.
Il y a aussi l'angle vie privee. Coller une reponse API de production dans un LLM tiers signifie envoyer tes donnees au serveur de quelqu'un d'autre. Si cette reponse contient des PII utilisateur, des endpoints internes, ou des tokens d'authentification qui se sont glisses dans le payload, tu viens de les partager avec un service externe. Pour des projets perso, personne ne s'en soucie. Pour des codebases de production avec des exigences de conformite, c'est une conversation avec ton equipe securite que tu ne veux pas avoir.
L'approche deterministe : meme entree, meme sortie, a chaque fois
Un convertisseur deterministe JSON vers TypeScript ne devine pas. Il parse. L'algorithme parcourt l'arbre JSON, inspecte le type JavaScript de chaque valeur, et le mappe au type TypeScript correspondant. Pas d'aleatoire, pas de parametre de temperature, pas de modele qui pourrait se comporter differemment un jeudi.

Les regles sont mecaniques :
"hello"est toujoursstring. Pas parfoisstring, pas occasionnellement"hello"comme type litteral.42est toujoursnumber. Pasint, pasfloat, pasnumber | string.[1, 2, 3]est toujoursnumber[]. PasArray<number>, pasnumber[] | undefined.{"a": 1}genere toujours une interface nommee separee aveca: number.nullest toujoursnull. Pasundefined, pas omis.
Meme JSON en entree, meme TypeScript en sortie. Execute-le cent fois et tu obtiens cent resultats identiques. C'est la propriete que tu veux d'un outil qui genere des definitions de types auxquelles ton compilateur fera confiance.

Ce que l'algorithme fait vraiment
Sous le capot, le convertisseur effectue une descente recursive dans ta structure JSON. Pour chaque valeur rencontree, il appelle inferType(), qui retourne la chaine du type TypeScript. Les objets produisent de nouvelles entrees d'interface dans un Map. Les tableaux inspectent leurs elements et produisent soit un type uniforme (string[]) soit un type union ((string | number)[]). Les tableaux vides deviennent unknown[] car il n'y a pas d'element pour inferer.
Les noms de proprietes sont convertis en PascalCase pour les noms d'interfaces. Les cles qui ne sont pas des identifiants JavaScript valides (tirets, espaces, chiffres en debut) sont automatiquement entre guillemets. La sortie peut basculer entre declarations interface et type.
Un exemple concret. Ce JSON :
{
"id": 1,
"name": "Aral Roca",
"email": "aral@example.com",
"active": true,
"roles": ["admin", "editor"],
"profile": {
"bio": "Full-stack developer",
"avatar": "https://example.com/avatar.png",
"social": {
"github": "aralroca",
"twitter": "aralroca"
}
},
"posts": [
{
"id": 101,
"title": "Understanding TypeScript Interfaces",
"published": true,
"tags": ["typescript", "tutorial"]
}
],
"createdAt": "2026-04-27T10:00:00Z"
}
Produit exactement ceci :
interface Root {
id: number;
name: string;
email: string;
active: boolean;
roles: string[];
profile: Profile;
posts: PostsItem[];
createdAt: string;
}
interface Profile {
bio: string;
avatar: string;
social: Social;
}
interface Social {
github: string;
twitter: string;
}
interface PostsItem {
id: number;
title: string;
published: boolean;
tags: string[];
}
Quatre interfaces. Chaque champ correctement type. Chaque objet imbrique extrait dans sa propre interface nommee. Aucun hasard implique.
Interface vs. type : quand la bascule compte
Le convertisseur offre les deux sorties interface et type. Le choix n'est pas cosmetique.
Les interfaces supportent le declaration merging ; si deux interfaces partagent le meme nom dans le meme scope, TypeScript fusionne leurs proprietes. Les types non. Pour les auteurs de bibliotheques qui veulent que les consommateurs etendent les types, les interfaces sont le meilleur choix.
Les types gerent les unions, intersections et mapped types plus naturellement. Si tu as besoin de type Result = Success | Error ou composer des shapes avec &, la sortie type t'epargne une etape.
Pour le typage de reponses API, ca compte rarement. Choisis ce qu'imposent les regles de linting de ton equipe et avance.
Ou l'inference deterministe necessite encore une revision humaine
Le convertisseur infere les types a partir des valeurs, pas des schemas. C'est une feature ; ca fonctionne avec n'importe quel JSON sans necessiter une spec OpenAPI ni JSON Schema. Mais ca signifie qu'il y a des cas limites ou tu voudras ajuster :
Champs optionnels. Le convertisseur ne voit que l'echantillon fourni. Si un champ est parfois absent de la reponse, ajoute ? manuellement.
Enums de string. "status": "active" devient string, pas "active" | "inactive" | "suspended". Restreins-le toi-meme.
Chaines de date. Les timestamps ISO 8601 comme "2026-04-27T10:00:00Z" sont string pour le convertisseur. Si tu les parses avec date-fns ou dayjs, tu voudras les changer en Date dans tes types finaux.
Wrappers de pagination. Une reponse comme { data: [...], meta: { page: 1, total: 100 } } genere une interface Root avec les deux. Renomme-la en PaginatedResponse<T> et extrais Meta comme generique.
Ces ajustements prennent des secondes. Le point est que le convertisseur deterministe te donne une ligne de base correcte ; les parties qui necessitent du jugement humain sont celles qu'une machine ne peut genuinement pas inferer d'un seul echantillon. Un LLM les raterait aussi ; la difference est que le LLM pourrait en plus se tromper sur les parties faciles.
La vie privee comme feature, pas comme slogan marketing
Le convertisseur tourne entierement cote client. Le JSON ne quitte jamais ton navigateur. Pas d'appel serveur, pas d'analytique sur ton input, pas de compte.
Ce n'est pas un benefice abstrait. Beaucoup d'equipes ont des politiques de securite qui interdisent d'envoyer du code source ou des reponses API a des services tiers. Ca exclut la plupart des outils en ligne. Ca exclut coller des reponses de production dans des chatbots LLM. Un convertisseur cote client qui traite tout dans une fonction JavaScript sur ta machine a zero surface de conformite.
Ouvre l'onglet reseau de ton navigateur pendant l'utilisation. Tu ne verras rien envoye.
Un workflow pratique
1. Obtenir une vraie reponse. Utilise curl, Postman ou l'onglet reseau de ton navigateur pour capturer une reponse API reelle.
2. Coller et convertir. Ouvre le convertisseur JSON vers TypeScript, colle le JSON, copie la sortie.
3. Renommer et affiner. Change Root en UserResponse. Ajoute ? ou c'est necessaire. Restreins les unions de strings.
4. Co-localiser avec ton client API. Je mets les types dans un types.ts a cote du fichier qui fait l'appel fetch ou axios.
5. Ajouter la validation runtime. Utilise Zod ou Valibot pour valider que l'API envoie vraiment ce que tes types decrivent. Le convertisseur te donne la structure ; une bibliotheque de schemas te donne des garanties runtime.
Le tout prend moins d'une minute par endpoint.
Au-dela des reponses API
Le convertisseur gere tout JSON valide :
- Fichiers de configuration. Colle
tsconfig.jsonoupackage.jsonpour un chargement de configuration type-safe. - Exports de base de donnees. Un document MongoDB ou une ligne PostgreSQL en JSON devient les types de ta couche ORM.
- Fixtures de test. Si tu ecris des tests avec Jest ou Vitest, convertir les fichiers fixture assure que tes mocks correspondent aux formes de production.
- Contenu CMS. Les reponses de CMS headless de Strapi, Sanity ou Contentful sont profondement imbriquees. Type-les une fois ; laisse le compilateur detecter les bugs de template.
Pour formater du JSON avant de convertir, le Formateur JSON gere le pretty-printing et la validation. Pour la direction opposee ; nettoyer du HTML en quelque chose qu'un LLM peut traiter efficacement ; il y a le convertisseur HTML vers Markdown.
La matrice des compromis
| Manuel | LLM | Deterministe | |
|---|---|---|---|
| Vitesse | Lente | Rapide | Rapide |
| Exactitude | Depend de toi | Majoritairement correcte | Toujours correcte pour l'echantillon |
| Coherence | Variable | Non deterministe | Identique a chaque fois |
| Vie privee | N/A | Donnees envoyees au serveur | Cote client uniquement |
| Champs optionnels | Tu decides | Devine parfois | Tu decides |
| Restriction de strings | Tu decides | Devine parfois | Tu decides |
Le convertisseur deterministe gere la partie mecanique; mapper des valeurs vers des types; parfaitement. Les parties qu'il ne peut pas gerer (optionnalite, enums de string, parsing de dates) sont les memes que les autres approches ne gerent pas non plus de maniere fiable. La difference est qu'il n'introduit pas de nouvelles erreurs sur les parties qu'il peut gerer.
Le verdict
La securite des types n'est pas un spectre. Tes types sont corrects ou ils ne le sont pas. Le typage manuel est lent et source d'erreurs a l'echelle. Le typage LLM est rapide mais probabiliste. La conversion deterministe est rapide et correcte; dans les limites de ce que n'importe quel outil peut inferer d'un seul echantillon JSON.
Utilise le convertisseur JSON vers TypeScript pour le travail mecanique. Garde ton jugement pour les champs optionnels, les unions de strings et le nommage d'interfaces; les decisions qui necessitent un contexte qu'aucun outil n'a.
Zero inscription. Zero envoi. Meme entree, meme sortie. Fait partie des Utilitaires de Developpement et Programmation sur Kitmul.
Photo par Florian Olivo sur Unsplash.