JSON Toolkit

Quelle structure une réponse d'API doit-elle utiliser

L’essentiel : Les objets nus sont propres jusqu'à ce qu'il faille des métadonnées ; les enveloppes complètes sont sûres mais verbeuses. Pour la plupart des projets, utilisez le statut HTTP pour les résultats et une enveloppe stable pour les données.

Intermédiaire~4 min de lecturePublié le 2026-10-30Mis à jour le 2026-10-30
Trois formes de réponse d'API et leurs compromis

L'essentiel : exprimez les résultats avec des codes de statut 4xx/5xx, portez la charge utile dans { "data": ..., "meta": ... }, et basculez sur une enveloppe error en cas d'échec ; c'est suffisant pour les API internes, et seules les plateformes publiques avec de nombreux intégrateurs ont besoin de JSON:API ou OpenAPI.

Écartez d'abord la pire option : toujours renvoyer 200 avec le résultat dans un champ code.

Trois formes

A. Objet nu

{ "id": 1, "name": "demo", "price": 99 }

Propre, et la sémantique HTTP fait le travail (404 est 404). Mais dès qu'il faut de la pagination, des avertissements ou un identifiant de trace, il n'y a nulle part où les mettre sans changer le contrat.

B. Enveloppe stable

{
  "data": { "id": 1, "name": "demo" },
  "meta": { "requestId": "abc", "took": 12 }
}

Charge utile et métadonnées vivent séparément, donc de nouvelles métadonnées ne remodelent jamais data. Coûte un niveau d'imbrication.

C. Enveloppe fourre-tout (à éviter)

{ "code": 0, "message": "success", "data": {...} }

La forme n'est pas le péché ; la pratique l'est. Un statut 200 pour chaque résultat signifie que CDN, passerelles, disjoncteurs, politiques de nouvelle tentative et tableaux de bord voient tous un succès. Chaque alerte a alors besoin d'un parsing personnalisé.

La recommandation

Statut HTTP pour le résultat, enveloppe pour la charge utile.

{
  "data": { "id": 1, "name": "demo" },
  "meta": { "requestId": "req_8f2a", "took": 12 }
}
{
  "error": {
    "code": "ORDER_NOT_FOUND",
    "message": "Order does not exist or was deleted",
    "details": [{ "field": "id", "issue": "not_found" }]
  },
  "meta": { "requestId": "req_8f2a" }
}

Statut 4xx ou 5xx ; code stable et lisible par machine ; message pour les humains ; details pour les erreurs de formulaire au niveau des champs.

Règles dures

  1. Les points de terminaison de liste doivent porter des métadonnées de pagination. Sans total ni curseur, il n'y a pas de pagineur.
  2. Les listes vides retournent [], jamais null. null.map est une panne récurrente.
  3. Horodatages en ISO 8601 avec un offset. Voir quel format pour les horodatages.
  4. Une seule convention de nommage. snake_case ou camelCase, convertie une fois à la frontière.
  5. Argent en unités mineures entières ou en chaînes. 99.99 n'est pas exact en flottant binaire.
  6. Ne confondez pas null et absent. Voir null ou omettre le champ.

Quand adopter une spécification

  • Plateformes publiques avec de nombreux intégrateurs : JSON:API ou OpenAPI plus une enveloppe partagée
  • API internes à une seule équipe : l'enveloppe ci-dessus suffit
  • Contrats forts : Schema d'abord, structures générées depuis lui

Les spécifications épargnent des discussions et coûtent de l'intégration. Pour une équipe de cinq personnes, JSON:API est généralement un travail négatif.

FAQ

Comment choisir entre un objet nu et une enveloppe ?

Utilisez une enveloppe quand il faut de la pagination, des avertissements ou un requestId, pour que meta puisse grandir sans remodeler data. Ne retournez l'objet nu que lorsque vous n'avez besoin d'aucune métadonnée et que vous acceptez que l'ajout de champs plus tard change le contrat.

Pourquoi une API ne devrait-elle jamais renvoyer 200 pour chaque résultat ?

CDN, passerelles, disjoncteurs, politiques de nouvelle tentative et tableaux de bord lisent tous le statut HTTP pour décider s'il faut mettre en cache, réessayer ou alerter. Avec 200 partout, ils traitent les échecs comme des succès, et chaque alerte a besoin d'un parsing personnalisé du code métier.

Les points de terminaison de liste doivent-ils porter des métadonnées de pagination ?

Oui. Sans total ni curseur, il n'y a pas de pagineur, et les listes vides doivent retourner [] plutôt que null, sinon l'appel .map du frontend casse.

Lectures complémentaires