Pourquoi le JSON généré par l’IA fait échouer JSON.parse() ? Causes et solutions

Au 17 septembre 2026 : passer une réponse de chat à JSON.parse() échoue surtout à cause des fences, du texte autour, des virgules finales, des coupures et du dialecte JS — pas parce que le modèle « ne sait pas écrire du JSON ». Carte des causes et ordre de réparation.

En bref : une erreur JSON.parse() ne signifie généralement pas « le modèle ne sait pas écrire du JSON ». Elle signifie que vous avez donné une réponse de chat entière à un parseur qui n’accepte qu’une valeur JSON.JSON.parse n’accepte qu’une seule valeur dans la grammaire JSON (ECMA-262 / RFC 8259). Barrières Markdown, prose d’emballage, virgules traînantes, sauts de ligne bruts, troncature et dialecte JS/Python lèvent tous un SyntaxError immédiatement. L’ordre de correction 2026 : si vous pouvez utiliser Structured Output ou lire les arguments de Tool Calling, ne parsez pas la prose de chat ; si vous devez parser, extraire, puis parse, puis valider avec JSON Schema — ne commencez pas par réparer au regex jusqu’à ce que « ça parse à peu près ».

Rédigé au 17 septembre 2026. Nous avons déjà couvert Qu’est-ce que le Structured Output ?, Comment l'IA génère du JSON conforme à JSON Schema, OpenAI vs Gemini Structured Output, JSON structuré avec l'API Gemini et Pourquoi le Tool Calling dépend de JSON Schema. Ce texte répond seulement à pourquoi la sortie modèle échoue à JSON.parse, et dans quel ordre la corriger.

Ce que JSON.parse accepte vraiment

Dans les navigateurs et Node, JSON.parse implémente le texte JSON, pas « un littéral d’objet JavaScript assez proche ». Les blancs (espace, tabulation, saut de ligne, retour chariot) peuvent entourer la valeur. Sinon l’entrée doit être exactement une valeur : objet, tableau, chaîne, nombre, true / false / null. Du non-blanc après cette valeur échoue — Chrome dit souvent Unexpected non-whitespace character after JSON.

Ces formes s’exécutent en JS et meurent en JSON. Les modèles les recopient sans cesse depuis les données d’entraînement :

FormeObjet JS / JSON5JSON.parse
Virgule traînante{"ok": true,} oklève
Quotes simples{'ok': true} oklève
Commentaires// note oklève
Clés nues{ok: true} oklève
undefined / NaN / Infinityexistent dans le langagelève
Saut de ligne brut dans une chaîneles template strings l’autorisentlève ; doit être \n

Déboguez avec une question : avez-vous passé « une valeur JSON », ou « un paragraphe que le modèle a emballé pour la lisibilité » ? Le parseur possède la première. Vous devez extraire la seconde.

Un tableau : classes d’échec

Classez le SyntaxError avant de lutter contre le libellé exact. Chrome, Safari et Node formulent le même bug différemment. Les classes sont peu nombreuses :

ClasseCe que le modèle émet souventRésultat typiqueFaites ceci d’abord
EmballageBarrières ```json, « voici le JSON »Le premier caractère n’est pas { / [Ôtez la barrière, puis découpez une valeur équilibrée
DialecteVirgules traînantes, quotes simples, commentaires, clés nuesUnexpected tokenPassez à Structured Output ; ne parsez pas comme du JS
Chaîne cassée" non échappé, sauts de ligne bruts, virgules pleine chasseLa chaîne se termine trop tôt, ou pas de : après une cléLisez la colonne ; plafonnez la longueur de champ
TroncatureObjet ou tableau non ferméUnexpected end of JSON inputAugmentez le plafond de sortie ; attendez le flux
Valeurs multiplesDeux valeurs JSON, ou de la prose après la premièreDes caractères après la première valeurNe découpez que la première valeur complète
EncodageBOM, caractères zero-width, double stringifyJeton bizarre, ou le parse rend une chaîneÔtez le BOM ; vérifiez typeof avant de parser à nouveau

Pour les agents, ajoutez encore : les arguments de Tool Calling sont souvent déjà un objet, ou une chaîne JSON déjà contrainte par le vendor. Ne faites pas passer tout le message assistant dans JSON.parse. C’est un autre canal — voir Pourquoi le Tool Calling dépend de JSON Schema.

Barrières et prose d’emballage

Les modèles de chat sont entraînés à mettre le code dans des barrières. Même si vous avez écrit « JSON uniquement », la réponse est souvent :

```json
{"ok": true, "id": "A-1024"}
```
Here is the result. I can explain the fields if you want.

Le premier caractère est un backtick, pas {. JSON.parse échoue à la colonne 0. Un « Bien sûr, voici le JSON : » en tête ou une disclaimer en queue, c’est le même bug. Pire : deux valeurs — un échantillon, puis le vrai résultat. Parser tout le blob et vous mourez après le premier }.

Extraire avec une règle : trouvez la première paire {} ou [] équilibrée (sautez les crochets dans les chaînes) et ne passez que cette tranche à JSON.parse. Ôtez d’abord les barrières. Ne coupez pas goulûment du premier { au dernier } — les crochets dans les chaînes, ou un second objet dans l’explication, découperont faux.

Dialecte : virgules traînantes, quotes simples, commentaires, clés nues

Les modèles ont vu des masses de JavaScript, Python, JSON5 et YAML. Quand on leur demande des « données structurées », ils mélangent les dialectes. Tout ce qui suit est illégal pour JSON.parse :

{
  ok: true,          // bare key + comment
  'name': 'Ada',     // single quotes
  "tags": ["a",],    // trailing comma
  "flag": True       // Python boolean
}

Ajoutez undefined, NaN, Infinity, None. Ils veulent dire quelque chose dans leurs langages ; JSON a null et des nombres finis. Remplacer JSON.parse par eval ou new Function pour « accepter » ces formes transforme le parseur en puits à code arbitraire. Ne le faites pas en production.

JSON5 et JSONC peuvent avaler commentaires et virgules traînantes. C’est bien pour des humains qui éditent de la config. C’est un mauvais parseur par défaut pour la sortie modèle. Une fois la grammaire relâchée, vous ne savez plus distinguer « une virgule en trop » d’« une chaîne cassée ». Si vous avez besoin d’une couche lâche, gardez-la derrière l’échec extraire + parse, et faites encore tourner le Schema après une réparation.

Chaînes et ponctuation : échappements, sauts de ligne, pleine chasse et guillemets typographiques

Une chaîne JSON légale utilise des guillemets doubles. Les " internes et les backslashes doivent être échappés. Les caractères de contrôle doivent être \n, \t ou \uXXXX. Quand un modèle recopie un commentaire utilisateur, quotes et sauts de ligne bruts atterrissent dans le champ. La chaîne se termine trop tôt ; la virgule suivante ou le caractère CJK devient un jeton inattendu.

La sortie CJK ajoute un jeu de saletés fréquent : virgule pleine chasse ,, deux-points pleine chasse :, et guillemets courbes “” / ‘’. Ils ressemblent à de la ponctuation ; leurs points de code ne sont pas 0x2C / 0x3A / 0x22. Ce « presque JSON » meurt après la valeur name :

{
  "name": "Ada",
  "ok": true
}

Ne corrigez pas cela avec une autre phrase « veuillez utiliser la ponctuation ASCII ». Mettez maxLength sur les champs string longs, faites citer le texte source plutôt que retaper la ponctuation, et utilisez Structured Output sur le canal final. Pour déboguer, collez dans le Validateur JSON et voyez à quelle colonne le surlignage s’arrête — une virgule pleine chasse est évidente.

Troncature et streaming : Unexpected end of JSON input

Unexpected end of JSON input signifie presque toujours que le texte s’est arrêté avant la grammaire : } manquant, ] manquant, ou une chaîne non fermée. En 2026 les sources habituelles sont un plafond de tokens de sortie, une coupure de sécurité, ou vous avez appelé JSON.parse sur un chunk de flux incomplet.

Une API streaming vous donne des deltas. Les premiers chunks peuvent être {"ok": tr. Parsez cela et vous échouerez. Faites plutôt :

  • Attendez la fin du flux (finish_reason / stop), puis parsez le buffer complet ;
  • Ou utilisez un vrai parseur JSON streaming qui avance token par token — n’appelez pas JSON.parse sur une demi-valeur ;
  • Si la raison d’arrêt est length / max_tokens, ce n’est pas un bug de parse. La génération n’a pas fini — augmentez le plafond, réduisez le Schema, ou paginez le modèle.

Auto-fermer les accolades après une troncature est un truc de brouillon. La forme peut parser et quand même manquer des champs ou couper une chaîne en deux. Après toute réparation, validez par Schema ; en échec, réessayez. Ne stockez pas en silence.

Caractères invisibles et double encodage

Un BOM UTF-8 (U+FEFF) n’est pas un blanc JSON. Certains chemins de copie et gateways le préfixent ; JSON.parse signale alors un jeton inattendu à la colonne 0. Les espaces zero-width et les traits d’union conditionnels font de même. Ôtez avec replace(/^\uFEFF/, ""), puis trim, avant d’extraire.

Le double encodage est plus discret. Un JSON.stringify produit la chaîne "{\"ok\":true}". Parser cette forme quotée donne la chaîne {"ok":true}, pas un objet. Un second parse donne l’objet. Si vous vous arrêtez après un parse et lisez .ok, vous obtenez undefined — ça a « parsé » et n’a aucun champ. Vérifiez typeof avant de parser à nouveau. Ne codez pas en dur « toujours parser deux fois » ; un vrai objet lèvera.

Ordre de correction : changer de canal, extraire, réparer en dernier

Cet ordre bat l’empilement de phrases de prompt :

  1. Changez de canal. Les réponses finales passent par Structured Output (OpenAI response_format.json_schema, Gemini responseMimeType plus Schema, Claude output_config.format). Les paramètres d’outil passent par les arguments de Tool Calling, pas par de la prose grattée. Voir Qu’est-ce que le Structured Output ?.
  2. Extraire. Ôtez les barrières ```json ; découpez la première valeur équilibrée ; jetez un BOM.
  3. Parser strictement. Utilisez seulement JSON.parse. En échec, gardez le texte brut et la position d’erreur. Pas de eval.
  4. Valider le Schema. Un parse réussi veut seulement dire que la grammaire est légale. Champs manquants, mauvais types et clés en trop demandent JSON Schema / ajv. Voir Comment l'IA génère du JSON conforme à JSON Schema.
  5. Réparer en dernier. Des outils comme jsonrepair peuvent fermer des accolades et retirer des virgules traînantes. Utilisez-les seulement après l’échec extraire + parse, et seulement si vous acceptez que les réparations peuvent changer le sens. Puis faites encore les étapes 3 et 4. Ne faites pas d’un réparateur le parseur par défaut global.

Les prompts aident encore : « pas de barrières, pas d’explication ». Ils baissent les chances d’un emballage. Ils ne remplacent pas un Schema, et ils ne relâchent pas JSON.parse. En 2026, traiter la prose de chat comme une API, c’est continuer à payer les barrières et la troncature.

Une petite pipeline extraire + parse

Une pipeline de taille pédagogique : ôter les barrières, jeter un BOM, découper une valeur équilibrée, puis JSON.parse. Elle gère les emballages courants. Elle ne répare pas les virgules traînantes ni la ponctuation pleine chasse — laissez cela à Structured Output ou à une couche de réparation explicite.

function stripFence(text) {
  const m = String(text).match(/```(?:json|JSON)?\s*([\s\S]*?)```/);
  return m ? m[1] : String(text);
}

function sliceBalancedJson(text) {
  const src = text.replace(/^\uFEFF/, "").trim();
  const start = src.search(/[\{\[]/);
  if (start < 0) throw new SyntaxError("No JSON value found");
  const open = src[start];
  const close = open === "{" ? "}" : "]";
  let depth = 0, inStr = false, esc = false;
  for (let i = start; i < src.length; i++) {
    const ch = src[i];
    if (inStr) {
      if (esc) { esc = false; continue; }
      if (ch === "\\") { esc = true; continue; }
      if (ch === '"') inStr = false;
      continue;
    }
    if (ch === '"') { inStr = true; continue; }
    if (ch === open) depth++;
    else if (ch === close) {
      depth--;
      if (depth === 0) return src.slice(start, i + 1);
    }
  }
  throw new SyntaxError("Unterminated JSON value");
}

function parseModelJson(raw) {
  return JSON.parse(sliceBalancedJson(stripFence(raw)));
}

Le slicer doit suivre s’il est dans une chaîne, sinon un { dans une valeur de champ ferme trop tôt. Objets et tableaux imbriqués utilisent depth. Si la tranche échoue encore à parser, collez le texte en échec dans le validateur et utilisez le tableau de classes ci-dessus. N’empilez pas plus de regex sur cette couche.

Voir l’erreur en local

N’envoyez pas la sortie modèle directement à un parseur de production. Dans le navigateur, vérifiez trois choses : est-ce du JSON légal ; sinon, quelle colonne ; si vous avez déjà un Schema, satisfait-il le contrat.

  • Validateur JSON — voyez où atterrit le SyntaxError ; attachez un Schema quand vous en avez un.
  • Formateur JSON — s’il formate, ça parse en général ; s’il échoue, cherchez virgules pleine chasse ou barrières dans la source.
  • JSON Diff — après un parse réussi, comparez l’objet modèle à l’objet minimal que vous autorisez.

Rien ne quitte le navigateur. Cela convient pour une réponse modèle en échec, un Schema et un blob d’arguments Tool Calling côte à côte. Stabilisez les noms de champs et required, puis câblez le Host.

FAQ

Pourquoi « ça ressemble à du JSON » échoue encore à JSON.parse ?

Les yeux humains tolèrent barrières, virgules traînantes, guillemets courbes et prose d’emballage. JSON.parse n’accepte qu’exactement une valeur RFC 8259. Ressembler à du JSON n’est pas être du JSON légal.

Un regex qui ôte les barrières ```json suffit-il ?

Non. Les barrières ne sont qu’un emballage. Il reste de la prose en queue, une seconde valeur JSON, des virgules traînantes et la troncature. Après avoir ôté les barrières, découpez une valeur équilibrée et parsez strictement.

En quoi JSON Mode diffère-t-il de Structured Output ?

JSON Mode ne contraint en général que « ça ressemble à du JSON », pas les champs et les types. Structured Output utilise JSON Schema pour bloquer les tokens illégaux au décodage. Si un programme consommera le résultat, privilégiez Structured Output. N’activez pas JSON Mode pour ensuite JSON.parse le corps de chat.

jsonrepair ou JSON5 doivent-ils être le parseur par défaut ?

Non. Ils acceptent une entrée qui devrait échouer, et ils peuvent changer le sens. Utilisez-les seulement comme couche de réparation après l’échec extraire + JSON.parse, puis validez encore par Schema.

Quand puis-je appeler JSON.parse sur une réponse streamée ?

Après la fin du flux et quand le buffer est une valeur complète. Parser un demi-chunk donne Unexpected end of JSON input à chaque fois. Pour consommer les tokens à l’arrivée, utilisez un parseur streaming, pas JSON.parse.

Le parse a réussi mais les champs sont faux. Est-ce cet article ?

C’est la couche suivante. JSON.parse ne garantit que la grammaire. Champs manquants, mauvais types et clés en trop sont des problèmes de Schema — voir les textes Structured Output et validation Tool Calling de ce site.

Résumé

Un échec JSON.parse est un problème de canal. Une autre phrase « veuillez sortir du JSON » ne le corrigera pas. Les modèles de chat emballent des barrières, mélangent les dialectes et s’arrêtent à un plafond de tokens. Le parseur n’accepte qu’une valeur JSON propre. En 2026, câblez le modèle via Structured Output ou les arguments Tool Calling ; puis extraire + parse strict + Schema ; réparer en dernier.

Les prompts peuvent réduire les emballages. Ils ne peuvent pas relâcher la grammaire. Collez le texte en échec dans un validateur local, voyez à quelle colonne il s’arrête, puis décidez : ôter une barrière, changer de canal, ou augmenter le plafond de sortie. Les modèles changent. Ce que JSON.parse accepte, et quel est votre contrat de champs, ne devrait pas.