JSON aux interfaces TypeScript: un guide pratique pour taper les réponses API
Apprenez à transformer JSON en interfaces TypeScript précises, à gérer des champs optionnels, des nuls, des unions et des tableaux, et à synchroniser les types avec de vraies API.
· 5 min de lecture
Chaque projet TypeScript qui communique avec une API finit par faire face à la même question: comment décrire la forme des données qui arrivent sous forme de JSON non tapée ? Laisser les réponses sous forme de any jette le principal avantage de TypeScript. Écrire des interfaces à la main pour une charge utile de 200 lignes est fastidieux et sujet à erreur. Ce guide vous explique un flux de travail fiable pour générer des interfaces à partir de JSON, les affiner et les garder honnêtes au fil du temps.
Pourquoi les types pour JSON importent
Lorsque vous appelez await res.json(), TypeScript vous donne any. À partir de ce moment, des fautes de frappe comme user.adress.city se compilaient sans se plaindre et échouaient seulement en temps d'exécution, souvent en production et souvent pour un sous-ensemble d'utilisateurs dont les données différaient. Une réponse tapée vous donne une compilation automatique, un refacteurage sûr et des erreurs de compilation lorsque le back-end renomme un champ.
Les types servent également de documentation. Un nouveau membre de l'équipe qui lit interface Order { id: string; items: OrderItem[]; total: number; couponCode?: string } en apprend plus en cinq secondes qu'en faisant défiler un échantillon de réponse.
Étape 1: Commencez par un échantillon réel et représentatif
Les types générés sont aussi bons que l'échantillon que vous fournissez. Avant de convertir, recueillez une réponse qui exerce le plus de champs possible:
- Choisissez un enregistrement avec des champs optionnels remplis, comme une commande qui a un coupon, une adresse de livraison et un message de cadeau.
- Incluez des tableaux avec au moins un élément; un tableau vide ne dit rien au générateur sur son contenu.
- Si un champ peut être
null, essayez d'inclure un exemple où il est nul et un autre où il ne l'est pas.
Copiez le corps de la réponse à partir de l'onglet Réseau de votre navigateur ou de curl, collez-le dans un [formatateur JSON] ((/tools/json) et confirmez qu'il est valide. Ensuite, passez au mode → TypeScript.
Étape 2: Générer le premier brouillon
Compte tenu de cette entrée:
{
"id": "ord_9f2",
"createdAt": "2026-09-30T12:01:44Z",
"customer": { "id": 42, "email": "ana@example.com" },
"items": [
{ "sku": "TSHIRT-M", "qty": 2, "price": 19.5 },
{ "sku": "MUG", "qty": 1, "price": 12 }
],
"coupon": null
}
Un générateur produit quelque chose comme:
export interface Customer {
id: number;
email: string;
}
export interface Item {
sku: string;
qty: number;
price: number;
}
export interface Root {
id: string;
createdAt: string;
customer: Customer;
items: Item[];
coupon: null;
}
C'est un point de départ solide, mais il reflète seulement ce qu'un échantillon contenait. Les étapes suivantes le transforment en un type correspondant au contrat API.
Étape 3: Renommer et affiner
Les générateurs nomment les interfaces imbriquées d'après des clés. Renommez-les en termes de domaine: Root devient Order, Item devient OrderItem. Ensuite, passez en revue chaque champ avec ces questions.
Peut-il manquer? Si l'API omet une clé dans certaines réponses, marquez-la comme optionnelle avec ?. Optionnel (coupon?: Coupon) et nullable (coupon: Coupon | null) sont différents: le premier signifie que la clé peut être absente, le second signifie qu'elle est toujours présente mais peut contenir null. Beaucoup d'API font les deux, que vous exprimez comme coupon?: Coupon | null.
** Est-ce que null était la seule valeur observée ?** Un champ écrit littéralement comme null signifie presque toujours que l'échantillon n'était pas représentatif. Remplacez-le par le vrai type plus | null.
Est-ce qu'une chaîne est vraiment une chaîne? Les dates arrivent comme des chaînes ISO 8601. Conservez-les comme string dans le type de transport; la conversion en Date appartient à une couche de cartographie. Vous pouvez documenter l'intention avec un alias de type: type ISODateString = string.
Est-ce qu'un nombre est une enum? Si status est toujours l'une des quelques valeurs, utilisez une union de lettres: status: "pending" | "paid" | "shipped". Cela donne une vérification exhaustive de switch.
** Les identifiants sont-ils sécurisés en tant que chiffres ?** Les identifiants de base de données supérieurs à 2^53 perdent de leur précision en JavaScript. Si votre backend utilise des entiers 64 bits, demandez-lui d'envoyer des identifiants sous forme de chaînes et tapez-les comme string.
Le résultat raffiné:
export type ISODateString = string;
export interface Order {
id: string;
createdAt: ISODateString;
status: "pending" | "paid" | "shipped" | "cancelled";
customer: Customer;
items: OrderItem[];
coupon?: Coupon | null;
}
Étape 4: Gérer les tableaux avec un contenu mixte
Certaines API renvoient des tableaux hétérogènes, comme un flux d'activité avec différents types d'événements. Un générateur naïf produit une seule interface avec chaque champ facultatif, ce qui perd de l'information. Un syndicat sans discrimination est beaucoup mieux:
type FeedEvent =
| { type: "comment"; id: string; text: string }
| { type: "like"; id: string; userId: string }
| { type: "follow"; id: string; followerId: string };
Maintenant, TypeScript rétrécit le type automatiquement à l'intérieur de if (event.type === "comment"). Lors de la conversion d'un tableau mixte, l'outil associe les types d'éléments déduits; utilisez cette sortie comme indice pour concevoir l'union discriminée à la main.
Étape 5: Validation à l'exécution
Les types TypeScript disparaissent au moment de la compilation. Si le back-end change, votre code reçoit toujours la nouvelle forme et vos types mentent silencieusement. Pour les données qui franchissent une limite de confiance, ajoutez la validation en temps d'exécution avec une bibliothèque de schémas telle que Zod, Valibot ou ArkType et dérivez le type statique du schéma:
import { z } from "zod";
const OrderSchema = z.object({
id: z.string(),
createdAt: z.string().datetime(),
items: z.array(z.object({ sku: z.string(), qty: z.number().int(), price: z.number() })),
coupon: z.object({ code: z.string() }).nullable().optional(),
});
export type Order = z.infer<typeof OrderSchema>;
const order = OrderSchema.parse(await res.json());
Vous pouvez toujours utiliser le générateur pour démarrer: générer l'interface, puis la traduire champ par champ dans le schéma.
6e étape: préférer la source de la vérité quand elle existe
Si votre API publie un schéma OpenAPI (Swagger) ou GraphQL, générez des types à partir de celui-ci au lieu d'échantillons. Des outils comme openapi-typescript et GraphQL Code Generator produisent des types exacts, y compris l'optionalité et les enum, et ils peuvent s'exécuter en CI afin que les types soient mis à jour chaque fois que le schéma change. La génération par échantillonnage est idéale pour les API tierces sans schémas, prototypes rapides, webhooks et services anciens.
l'interface ou le type ?
Pour les formes d'objets, interface et type sont presque interchangeables. Les interfaces peuvent être étendues et fusionnées, et les messages d'erreur se réfèrent à elles par leur nom, ce qui aide avec les objets plus grands. Les alias de type sont requis pour les unions, les tuples et les types mappés. Une convention commune est les interfaces pour les formes d'objets et les alias de type pour tout le reste.
Les pièges courants
- En tapant tout comme facultatif. Cela semble sûr mais impose des vérifications nulles partout et cache de vraies violations de contrat. Rendre les champs facultatifs uniquement lorsque l'API les omet.
- Confiance dans un seul échantillon. Vérifiez la documentation ou plusieurs réponses avant de finaliser l'option.
- Mélange des types de transport et de domaine. Gardez un
OrderDTObrut qui reflète JSON et le cartographier dans un domaineOrderavec des objetsDateréels et des champs calculés. - Oubliant les enveloppes de pagination. Beaucoup d'API enveloppent des listes dans
{ data: [...], nextCursor: "..." }. Modélisez ceci une fois comme un générique:interface Page<T> { data: T[]; nextCursor: string | null }.
Une liste de contrôle rapide
- Prenez un échantillon riche et réel et vérifiez-le.
- Générer des interfaces avec le convertisseur [JSON vers TypeScript] (tools/json).
- Renommer les types en langage de domaine.
- Correction des champs facultatifs par rapport aux champs nullables, des unions littérales et des types d'ID.
- Remplacez les syndicats mixtes par des syndicats discriminés.
- Ajouter la validation en temps d'exécution pour les données externes.
- Passez à la génération basée sur le schéma lorsqu'un schéma OpenAPI ou GraphQL existe.
Suivre ces étapes prend quelques minutes par point final et se rembourse plusieurs fois en réacteurs de complétion automatique et plus sûrs et en bugs détectés avant qu'ils n'atteignent les utilisateurs.
Cette page a été traduite automatiquement depuis l'anglais. Si vous repérez une erreur, dites-le-nous.