Une petite précision sur la raison pour laquelle une société de proxy rédige cet article : nous sommes Geonode, et l'erreur JSON la plus courante signalée par nos clients est « Unexpected token '<' » sur les données qu'ils ont récupérées. Cela signifie que la réponse était au format HTML — une page d'erreur, une redirection vers une page de connexion ou une page de blocage — et que l'analyseur signale à juste titre qu'il n'a pas reçu de données au format JSON. Avant de modifier le moindre code, enregistrez les 200 premiers caractères de ce que vous avez reçu. Presque tout le reste de cet article part du principe que le fichier est bel et bien au format JSON, et c’est cette hypothèse qui s’avère le plus souvent erronée.
Dans Node : lecture à partir du disque
Trois approches, dont l'une constitue la solution moderne.
**fs/promises
, la méthode standard :**
import { readFile } from "node:fs/promises";
const raw = await readFile("./data.json", "utf8");
const data = JSON.parse(raw);
La documentation Node est très claire concernant l’argument d’encodage, et cela a son importance : sans encodage, readFile
« renvoie une promesse qui se résout en un objet <Buffer>
contenant le contenu du fichier » ; avec un encodage, elle « se résout en un <string>
». JSON.parse
accepte un Buffer en le convertissant de force en chaîne de caractères ; omettre l’encodage fonctionne donc généralement et entraîne une conversion supplémentaire inutile. Passez "utf8"
.
Il prend également en charge une AbortSignal
via l’option signal
, « vous permettant d’interrompre une opération de readFile
en cours » — ce qui est utile lorsqu’une lecture fait partie d’une requête susceptible d’être annulée.
Synchrone, pour le code de démarrage :
import { readFileSync } from "node:fs";
const config = JSON.parse(readFileSync("./config.json", "utf8"));
Le blocage est acceptable avant que votre serveur ne commence à répondre aux requêtes. Il ne l’est pas à l’intérieur d’un gestionnaire de requêtes, où il bloque la boucle d’événements pour toutes les autres connexions. Cette distinction résume toute la règle.
**require
, en CommonJS uniquement :**
const data = require("./data.json");
Concise, cette méthode présente deux propriétés que l’on a tendance à oublier. Elle met en cache : ainsi, une seconde require
du même chemin renvoie le même objet sans relire le fichier — ce qui signifie que la modification du fichier à l’exécution n’a aucun effet. Elle n’est pas disponible dans les modules ES.
Importation d'attributs : la méthode standard
La syntaxe que la plupart des gens n'ont pas encore adoptée, et celle qu'il convient d'utiliser dans le nouveau code.
import data from "./data.json" with { type: "json" };
Ou de manière dynamique :
const data = await import("./data.json", { with: { type: "json" } });
MDN classe cela dans la catégorie Baseline 2025, disponible depuis avril 2025 sur les navigateurs les plus récents, les environnements d'exécution non liés aux navigateurs, tels que Node et Deno, s'alignant sur la sémantique des navigateurs pour les modules JSON.
L’attribut ``type: "json"`
n’est pas une simple décoration. MDN explique qu’il « vérifie qu’un module est servi avec le type MIMEapplication/json`
», et que si le fichier « est servi avec un type de média autre que application/json
, l’importation échouera ».
Il convient de citer la justification en matière de sécurité, car elle explique pourquoi cet attribut est obligatoire et non facultatif :
Si, pour une raison quelconque (par exemple, si le serveur est piraté ou frauduleux), le type de média dans la réponse du serveur est défini sur
text/javascript
(pour le code source JavaScript), alors le fichier serait analysé et exécuté en tant que code. Si le fichier « JSON » contient effectivement du code malveillant, la déclaration ``import`
` exécuterait involontairement du code externe, ce qui constituerait une menace sérieuse.
Remarque concernant la migration : une proposition antérieure utilisait le mot-clé « assert
» à la place de « with
». MDN signale qu’il s’agit d’un changement rompant la compatibilité — les implémentations utilisant « assert
» « ne sont plus prises en charge ». Si vous trouvez « assert { type: "json" }
» dans un code plus ancien ou dans un tutoriel, il convient de le mettre à jour.
Une gestion des erreurs qui vous apporte des informations
L'habitude qui fait la différence entre un problème résolu en cinq minutes et un qui en prend une heure.
function parseJson(text, source) {
try {
return JSON.parse(text);
} catch (err) {
throw new Error(
`Failed to parse JSON from ${source}: ${err.message}. ` +
`First 200 chars: ${text.slice(0, 200)}`
);
}
}
C'est la « tranche » qui compte. JSON.parse Les erreurs indiquent une position et un caractère ; c'est la saisie réelle qui vous explique pourquoi. Trois signatures couvrent la plupart des cas :
Unexpected token '<' — le contenu est du HTML. Une page d’erreur, une redirection de connexion ou une liste de répertoires.
Unexpected end of JSON input — l’entrée est vide ou tronquée. Une réponse 204, un fichier qui n’a pas pu être écrit entièrement, ou une requête dont vous avez oublié d’attendre la réponse.
Unexpected token '}' à un emplacement plausible — JSON véritablement mal formé, souvent en raison d’une virgule de fin. JSON les interdit, même si JavaScript les autorise.
Pour la lecture de fichiers, il faut distinguer l’échec de lecture de l’échec d’analyse. ENOENT signifie que le fichier n’existe pas, ce qui est un problème différent d’un contenu invalide et mérite un message différent.
Vérifier ce que vous lisez
L'analyse syntaxique a réussi. Cela vous indique simplement que la syntaxe était valide, mais ne dit absolument rien sur la conformité des données aux attentes de votre code — et c'est précisément dans cet écart entre les deux que se cache une part surprenante des défaillances en production.
L’analyse syntaxique et la validation sont deux étapes distinctes. JSON.parse
renverra sans problème {"user": {"nmae": "Ada"}}
même si la clé contient une faute de frappe, ou un champ price
contenant la chaîne "n/a"
alors qu’un nombre était attendu. Votre code échouera alors en aval, à plusieurs fonctions du problème réel, avec une erreur qui désigne un symptôme plutôt qu’une cause.
Pour tout ce qui échappe à votre contrôle, effectuez une validation par rapport à un schéma. Plusieurs bibliothèques gèrent bien cela, et le principe est le même quelle que soit celle que vous choisissez :
const Config = z.object({
port: z.number().int().min(1).max(65535),
host: z.string(),
retries: z.number().int().default(3),
features: z.array(z.string()).optional(),
});
const config = Config.parse(JSON.parse(await readFile("./config.json", "utf8")));
L’erreur indique désormais le nom du champ, le type attendu et ce qui a été trouvé — ce qui fait la différence entre une correction en cinq minutes et un après-midi de travail.
Pour les cas simples, quelques assertions ne coûtent rien :
const data = JSON.parse(raw);
if (!Array.isArray(data.items)) throw new Error("items must be an array");
if (data.items.length === 0) throw new Error("items is empty — check the source");
Cette deuxième vérification vaut plus qu’il n’y paraît. Un tableau vide est un JSON valide, s’analyse correctement, et constitue très souvent un symptôme plutôt qu’un résultat légitime — une API qui n’a rien renvoyé parce qu’un filtre était erroné, ou un scraping qui a réussi sur une page qui avait changé.
Méfiez-vous des nombres que vous n’avez pas générés vous-même. Les nombres JSON sont convertis en doubles JavaScript ; ainsi, les entiers supérieurs à Number.MAX_SAFE_INTEGER
perdent de la précision de manière silencieuse, sans générer d’erreur à aucun moment. Les identifiants en font généralement les frais : deux enregistrements distincts d’une base de données peuvent être analysés en une même valeur. Si un champ est un identifiant plutôt qu’une quantité, il doit être une chaîne de caractères dans le JSON — et si vous ne contrôlez pas le producteur, l’argument « context.source
» de la fonction de reconstitution vous fournit les chiffres d’origine.
Et effectuez la validation à l’entrée, une seule fois. Vérifier le format des données dès leur entrée dans votre programme permet à tout ce qui se trouve en aval de partir du principe qu’elles sont correctes. Effectuer des vérifications préventives à vingt endroits différents signifie vingt endroits à mettre à jour et aucun point unique où le contrat est formellement défini.
Fichiers volumineux : l’
JSON.parse
ation est synchrone et nécessite que l’intégralité du document soit en mémoire. Ces deux aspects posent problème à mesure que la taille des fichiers augmente.
Règle générale. En dessous d’un mégaoctet, ne vous en préoccupez pas. Entre un et dix mégaoctets, évaluez la situation — en particulier sur le thread principal d’un navigateur, où l’analyse bloque le rendu et entraîne des saccades visibles. Au-delà de dix mégaoctets, ou d’environ un dixième de votre mémoire disponible, optez pour une autre solution.
Déplacez l’opération hors du thread principal. Dans un navigateur, un Web Worker effectue l’analyse sans bloquer l’interface. Sous Node, un thread de travail fait de même pour la boucle d’événements.
Utilisez du JSON délimité par des sauts de ligne. Il s’agit d’une solution structurelle plutôt que d’une solution de contournement. Un document JSON par ligne signifie que vous traitez un fichier de n’importe quelle taille en mémoire constante, que chaque ligne est analysée indépendamment, et qu’un fichier tronqué fournit tout de même l’intégralité de chaque enregistrement :
import { createReadStream } from "node:fs";
import { createInterface } from "node:readline";
const rl = createInterface({ input: createReadStream("./data.jsonl") });
for await (const line of rl) {
if (line.trim()) handle(JSON.parse(line));
}
Si vous contrôlez le format, c’est la meilleure conception pour tout ce qui s’ajoute au fil du temps — et la raison pour laquelle une tâche de collecte qui plante laisse un fichier utilisable plutôt qu’un fichier impossible à analyser.
Utilisez un analyseur en continu lorsque le format est un seul grand tableau que vous ne pouvez pas modifier. Plusieurs bibliothèques renvoient les valeurs au fur et à mesure de leur réception plutôt que de construire l’arborescence dans son intégralité.
Ou demandez-en moins. Pagination, sélection de champs, un point de terminaison plus restreint. C’est presque toujours la bonne réponse, mais elle est presque toujours ignorée car elle nécessite de s’adresser au responsable de l’API.
Écriture de données JSON
En bref, voici l’inverse, car c’est généralement la question suivante.
import { writeFile } from "node:fs/promises";
await writeFile("./out.json", JSON.stringify(data, null, 2), "utf8");
Les arguments ``null, 2`
` produisent une sortie indentée, ce qui est plus important qu’il n’y paraît : un fichier destiné à être lu par un humain ou comparé dans un système de contrôle de version doit être formaté, tandis qu’un fichier destiné à être transmis ne doit pas l’être.
Trois éléments ne survivent pas à la fonction ``JSON.stringify`
et entraînent une perte de données silencieuse plutôt que des erreurs. Les valeurs ``undefined
et les fonctions sont entièrement supprimées des objets et deviennent des ``null
à l’intérieur des tableaux. Les objets ``Date
deviennent des chaînes ISO ; ils ne peuvent donc pas être reconvertis en dates sans un convertisseur. Et ``BigInt
` provoque directement une exception — l’approche standard consiste à sérialiser les grands entiers sous forme de chaînes.
Pour ajouter des éléments, écrivez du JSON délimité par des sauts de ligne plutôt que de réécrire un tableau :
import { appendFile } from "node:fs/promises";
await appendFile("./log.jsonl", JSON.stringify(record) + "\n", "utf8");
L'ajout d'éléments à un tableau JSON nécessite de le lire, de l'analyser, d'y ajouter des éléments et de réécrire l'intégralité du fichier — ce qui est coûteux en ressources et peut corrompre le fichier si le processus s'interrompt en cours d'écriture.
Questions fréquentes
Comment lire un fichier JSON dans Node.js ?
const data = JSON.parse(await readFile("./data.json", "utf8")) en utilisant node:fs/promises. Vous pouvez également utiliser la syntaxe d'importation standard : import data from "./data.json" with { type: "json" }, qui fait partie de la norme de base depuis 2025 et fonctionne aussi bien dans Node que dans les navigateurs.
JavaScript peut-il lire un fichier local dans le navigateur ?
Pas par chemin d’accès. Les navigateurs ne peuvent pas ouvrir n’importe quel fichier local, ce qui constitue une restriction de sécurité délibérée. L’utilisateur doit sélectionner un fichier via une boîte de dialogue « <input type="file"> » ou par glisser-déposer, après quoi File.text() vous fournit le contenu.
À quoi sert l’attribut « import ... with { type: "json" } » ?
Il importe un fichier JSON en tant que module tout en vérifiant que le serveur l’a bien servi avec le type MIME « application/json ». Sans cette vérification, un fichier servi sous la forme « text/javascript » serait analysé et exécuté comme du code — ce qui constitue le problème de sécurité que cet attribut est censé empêcher.
Pourquoi obtiens-je le message « Unexpected token '<' » lors de la lecture d’un fichier JSON ?
Parce que le contenu commence par <, ce qui signifie que vous avez reçu du code HTML plutôt que du JSON — généralement une page d’erreur ou une redirection vers une page de connexion. Avec fetch, la cause est presque toujours l’absence de vérification res.ok, puisque fetch ne rejette pas les statuts d’erreur HTTP.
Dois-je utiliser require ou import pour le JSON ?
Utilisez import ... with { type: "json" } dans votre nouveau code, car c’est la norme et cela fonctionne avec les modules ES. require est réservé à CommonJS et met le résultat en cache ; ainsi, un fichier modifié à l’exécution ne sera pas relu. Aucune de ces deux méthodes n’est adaptée aux fichiers qui changent pendant l’exécution de votre programme — utilisez plutôt readFile dans ce cas.
Comment lire un très gros fichier JSON ?
Déplacez l’analyse hors du thread principal à l’aide d’un worker, ou restructurez les données sous forme de JSON délimité par des sauts de ligne afin que chaque ligne soit analysée indépendamment en mémoire constante. Pour un grand tableau immuable, utilisez un analyseur en continu. Et demandez-vous si vous pouvez demander moins de données dès le départ.
Quelle est la différence entre JSON.parse et response.json() ?
Response.json() lit le corps d’une requête et l’analyse en une seule étape ; cette méthode n’accepte pas de fonction de réactivation. JSON.parse fonctionne sur une chaîne de caractères dont vous disposez déjà et accepte une telle fonction. Si vous avez besoin d’une fonction de réactivation — pour les dates ou les grands entiers —, utilisez res.text() suivi de JSON.parse.
Comment gérer un fichier JSON qui pourrait ne pas exister ?
Traitez l’erreur de lecture séparément de l’erreur d’analyse. Sous Node, un code d’erreur « ENOENT » signifie que le fichier est manquant, ce qui nécessite généralement une valeur par défaut plutôt qu’un échec — tandis qu’un code « SyntaxError » signifie que le fichier existe mais que son contenu est incorrect.
Conclusion
La méthode dépend de l'environnement, et la solution moderne est plus uniforme qu'auparavant. La méthode « import data from "./data.json" with { type: "json" } » fonctionne sous Node et dans les navigateurs, fait partie des normes de base à partir de 2025 et inclut une vérification du type MIME qui répond à un véritable besoin de sécurité plutôt qu'à une simple formalité.
Pour les fichiers qui changent pendant l’exécution de votre programme, lisez-les explicitement : readFile avec un encodage "utf8" dans Node, fetch avec une vérification res.ok dans un navigateur, et File.text() pour un fichier choisi par l’utilisateur. Cette vérification res.ok est la ligne la plus importante de cet article, car la négliger est la cause directe de l’erreur JSON la plus courante qui soit.
En cas d’échec, consignez les 200 premiers caractères de l’entrée avant de modifier le code. « Unexpected token '<' » signifie qu’il s’agit de HTML, « Unexpected end of JSON input » signifie que le contenu est vide ou tronqué ; dans les deux cas, la réponse s’obtient en examinant ce qui a réellement été reçu plutôt qu’en se basant sur le fonctionnement du parseur.
Et si les fichiers deviennent trop volumineux, la solution structurelle consiste à utiliser du JSON délimité par des sauts de ligne plutôt que de recourir à une machine plus puissante. Un document par ligne s’écoule en mémoire constante, s’ajoute en toute sécurité et résiste à une écriture interrompue en conservant chaque enregistrement complet intact.
