Aller au contenu
Allin

CSV vers JSON : les cinq cas qui cassent tous les convertisseurs

Publié le 17/07/2026 · 16 min de lecture · Outils pour développeurs

Daniel Okonkwo

Daniel OkonkwoDéveloppeur front-end et rédacteur Tech chez Allin

Performance web · Formats de fichiers

Vérifié à partir de 4 sources

Voir le profil
En bref

Le CSV n'a pas de norme, seulement la RFC 4180 — une RFC informative qui décrit ce que la plupart des programmes faisaient déjà en 2005, pas une règle qui s'impose. Cinq cas séparent un analyseur qui fonctionne d'un analyseur qui corrompt en silence. Un : un délimiteur dans un champ entre guillemets. name,city / Tom,"Paris, France" reste sur deux colonnes, et un guillemet doublé à l'intérieur ressort en un seul. Deux : un saut de ligne dans un champ entre guillemets. L'analyseur lit jusqu'au guillemet fermant et non jusqu'à la fin de ligne, donc une adresse sur deux lignes survit en une seule valeur. Trois : les types. La conversion est désactivée par défaut ; active-la et 1 devient le nombre 1, tandis que 0044, 1.0, 1e3, 2026-08-18 et 9007199254740993 restent des chaînes, car une cellule n'est convertie que si le nombre se réaffiche exactement comme il est arrivé. true devient un booléen mais TRUE non, les booléens JSON étant en minuscules, et null devient le null JSON — ce qui surprendra le jour où un nom de famille est Null. Quatre : les en-têtes en double. name,name,name donne name, name_2 et name_3 au lieu d'une seule colonne survivante, et un en-tête vide devient column2. Cinq : l'encodage. Un BOM UTF-8 est retiré, et le navigateur absorbe les BOM UTF-8 et UTF-16 avant que l'outil ne voie le texte — mais il n'y a pas de sélecteur d'encodage, si bien qu'un export Windows-1252 arrive en Andr�, et U+FFFD ne se défait pas. Deux cas qu'il ne rattrape pas : un champ entre guillemets précédé d'une espace, Tom, "Paris, France", n'est pas traité comme cité, et une ligne d'indication Excel sep=; est consommée comme ligne d'en-tête.

Délimiteurs entre guillemets, sauts de ligne intégrés, types ambigus, en-têtes en double et encodage. Chaque cas a été passé dans le convertisseur et la sortie exacte est reproduite ici — y compris les deux qu'il ne rattrape pas.

La RFC 4180 est une description, pas une règle

Tout le monde cite la RFC 4180 comme si c'était la norme CSV. Son propre en-tête dit le contraire : elle est informative, ce qui, en langage IETF, signifie qu'elle ne spécifie aucune norme Internet. Elle a été publiée en 2005 pour consigner ce que les programmes faisaient déjà, et elle le dit à propos de son propre sujet : la règle 5 note que certains programmes, dont Microsoft Excel, n'utilisent pas de guillemets du tout. C'est la racine de tous les problèmes de cet article. Il n'existe aucune autorité à invoquer quand deux outils divergent sur le même fichier, puisque aucun des deux ne viole quoi que ce soit.

La RFC définit bien deux choses qui aideraient, et aucune ne survit au passage par un fichier. Elle définit un paramètre header sur le type de média text/csv, avec les valeurs present et absent, pour indiquer au destinataire si la première ligne contient des noms de colonnes. Et elle indique que l'usage courant est l'US-ASCII, les autres jeux de caractères étant portés par le paramètre charset. Ce sont deux paramètres MIME : ils vivent sur une réponse HTTP ou une pièce jointe, pas dans les octets. Enregistre les mêmes données sous ventes.csv et les deux informations ont disparu. C'est pour cela que tout lecteur CSV au monde propose une case « la première ligne est un en-tête », et que l'encodage doit être deviné.

Cas 1 et 2 — le délimiteur et le saut de ligne dans un champ cité

Ces deux cas sont le même bug sous deux costumes, et les deux viennent d'une découpe sur le caractère brut au lieu d'une véritable analyse. Un convertisseur qui fait text.split(",") transforme Tom,"Paris, France" en trois colonnes, et toutes les lignes en dessous héritent de la colonne en trop. Un convertisseur qui fait d'abord text.split("\n") coupe en deux une adresse sur deux lignes et produit une ligne à un seul champ. Le remède est le même : parcourir la chaîne caractère par caractère, tenir un indicateur « suis-je entre guillemets », et ne traiter un délimiteur ou un saut de ligne comme structurel que lorsque l'indicateur est baissé.

Les deux passent. name,address / Tom,"12 rue A\nParis" / Ann,"3 rue B" renvoie exactement deux objets, le premier avec une adresse sur deux lignes. Un guillemet doublé est déséchappé à l'entrée, donc "He said ""hi"" loudly" ressort en He said "hi" loudly. Un écart avec la RFC mérite d'être connu : l'outil normalise tout CR LF en LF avant l'analyse, si bien qu'un saut de ligne qui était un CR LF dans un champ cité sort du convertisseur en LF simple. Rien n'est perdu, mais si tu compares le trajet aller-retour octet à octet, c'est là que se trouve la différence.

Le cas qu'il ne rattrape pas est celui sur lequel la RFC est explicite. La section 2.4 dit que les espaces font partie du champ et ne doivent pas être ignorées : dans Tom, "Paris, France", le premier caractère du champ est une espace et le guillemet qui suit n'est qu'un caractère, pas un délimiteur ouvrant. L'analyseur donne raison à la RFC et produit deux colonnes cassées, " Paris et France". La plupart des gens qui écrivent cette ligne voulaient un champ cité. Si ton exporteur ajoute une espace après le délimiteur, retire-la avant de convertir, sinon les guillemets sont purement décoratifs.

Cas 3 — les types, et le test aller-retour qui sauve tes numéros de téléphone

Le CSV n'a pas de types. Chaque cellule est du texte, et dès que tu produises du JSON il faut décider si 1 est la chaîne "1" ou le nombre 1. Par défaut, le convertisseur ne décide rien : la conversion est une bascule et elle démarre désactivée, si bien qu'une exécution simple te donne un tableau d'objets dont toutes les valeurs sont des chaînes. C'est le bon défaut, car une chaîne est toujours récupérable, un nombre non.

Active la bascule et la règle tient en un test aller-retour : une cellule devient un nombre uniquement si réimprimer ce nombre redonne exactement les caractères arrivés. À l'exécution, 1 devient 1, mais 0044 reste "0044" parce que Number("0044") s'imprime 44. 1.0 reste "1.0" parce qu'il s'imprime 1. 1e3 reste "1e3" parce qu'il s'imprime 1000. .5 et +1 restent des chaînes pour la même raison. Et 9007199254740993 reste une chaîne, parce que le double le plus proche en JavaScript s'imprime 9007199254740992 : un convertisseur sans ce garde-fou change en silence le dernier chiffre d'un grand identifiant, et rien en aval ne t'le dira jamais.

Trois choses échappent au test aller-retour, parce qu'elles passent par une comparaison littérale. true et false deviennent des booléens, mais en minuscules seulement : TRUE, True et FALSE restent des chaînes, ce qui compte puisque Excel écrit les booléens en majuscules et que les interfaces française et allemande écrivent VRAI et WAHR. null devient le null JSON, et c'est un vrai piège : un champ texte dont la valeur est les quatre lettres null n'est pas la même chose qu'une valeur absente, et après conversion tu ne peux plus les distinguer. Et rien n'est fait des dates : 2026-08-18 reste la chaîne "2026-08-18", ce qui est la bonne réponse, car un convertisseur qui analyse les dates doit choisir un fuseau horaire et se trompera.

Cas 4 — en-têtes en double, en-têtes vides, lignes irrégulières

Un CSV a le droit de répéter un nom de colonne ; un objet JSON non. Avec name,name,name au-dessus de a,b,c, l'implémentation naïve écrit trois fois la même clé et JSON garde la dernière : tu obtiens {"name": "c"} et deux colonnes de données ont disparu sans la moindre erreur. Ce convertisseur renomme : name, name_2, name_3. Il gère aussi le cas suivant, quand le nom inventé entre en collision avec un vrai — name,name,name_2 donne name, name_2 et name_2_2, parce que le renommeur vérifie contre tout ce qui est déjà utilisé et pas seulement contre les en-têtes d'origine.

Une cellule d'en-tête vide reçoit un nom positionnel : name,,name, au-dessus de a,b,c,d donne name, column2, name_2 et column4. Les noms d'en-tête sont détourés, donc " name , age " produit name et age. Et la largeur de sortie est celle de la ligne la plus large du fichier, pas celle de l'en-tête : a,b,c au-dessus des deux lignes 1,2 et 3,4,5,6 donne quatre clés à chaque objet, avec c vide sur la ligne courte et un column4 qui porte le 6 dont l'en-tête n'avait pas tenu compte. Rien n'est jeté, et c'est le bon choix pour un convertisseur : tronquer en silence une ligne trop longue, c'est détruire précisément la ligne qu'il fallait regarder.

Cas 5 — l'encodage, celui que l'outil ne peut pas réparer

Un fichier CSV, ce sont des octets. Rien à l'intérieur ne dit quelle table transforme ces octets en caractères, et la RFC 4180 place cette information dans un paramètre MIME qu'un fichier sur disque ne porte pas. Deux mécanismes comblent partiellement l'écart. Une marque d'ordre des octets en tête de fichier identifie UTF-8, UTF-16 LE et UTF-16 BE, et le lecteur de fichiers du navigateur la consomme : dépose un fichier UTF-16 LE avec BOM dans l'outil et le texte arrive correctement décodé, la marque déjà retirée. L'outil retire ensuite un BOM de son côté, ce qui rattrape le cas où la marque arrive par le presse-papiers plutôt que par un fichier.

L'écart qui reste ouvert, c'est le fichier sans aucune marque — c'est-à-dire la plupart. Enregistre un tableur en CSV simple sur une machine Windows d'Europe occidentale et tu obtiens du Windows-1252, un octet par caractère, sans BOM. Ce convertisseur n'a pas de sélecteur d'encodage : le lecteur retombe sur UTF-8, l'octet E9 qui signifiait é n'est pas de l'UTF-8 valide, et il est remplacé par U+FFFD. L'outil analyse ensuite proprement et renvoie {"name": "Andr�", "city": "K�ln"} sans le moindre avertissement, puisque de son point de vue rien n'a échoué. U+FFFD ne garde aucune trace de l'octet remplacé : ce n'est donc pas réparable après coup. Réexporte le fichier en UTF-8, ou colle le texte au lieu de déposer le fichier, car le texte du presse-papiers a déjà été décodé par l'application qui le détient.

Un dernier cas d'encodage a le tranchant vif : l'UTF-16 sans marque d'ordre des octets. Il n'y a rien à renifler, le fichier est donc lu comme de l'UTF-8, un octet sur deux est un zéro, et ce qui revient est un objet unique dont la clé contient des caractères NUL. Cela ressemble à du charabia plutôt qu'à du texte légèrement faux, et c'est la bonne issue : tu le verras immédiatement. Les échecs dangereux sont les silencieux, et le Windows-1252 lu comme de l'UTF-8 est le plus silencieux de tous, parce que les colonnes s'alignent parfaitement et que seules les lettres accentuées sont fausses.

Le sixième cas que personne ne cite : la ligne sep=

Excel accepte une première ligne de la forme sep=; comme une instruction indiquant quel caractère sépare les champs, et beaucoup de routines d'export l'émettent pour qu'un fichier à points-virgules s'ouvre correctement chez un lecteur dont le séparateur de liste est la virgule. Elle ne figure pas dans la RFC 4180 et n'y a jamais figuré : c'est une convention d'éditeur qui s'est répandue parce qu'elle fonctionne. Pour un convertisseur qui n'en a jamais entendu parler, c'est simplement le premier enregistrement du fichier.

C'est exactement ce qui se passe ici. Donne au convertisseur sep=; suivi de Name;Ville;Montant et de deux lignes de données : la détection automatique choisit correctement le point-virgule — parce que la ligne sep= en contient un — mais l'étape d'en-tête la consomme. Tu obtiens trois objets au lieu de deux, avec les clés "sep=", column2 et column3, et les vrais noms de colonnes Name, Ville et Montant apparaissent comme valeurs du premier. C'est évident dès qu'on regarde la sortie, et invisible si on l'enchaîne directement ailleurs. Supprime la première ligne avant de convertir, ou convertis d'abord le délimiteur et laisse le convertisseur de délimiteur réécrire l'indication à ta place.

Cinq entrées passées dans le convertisseur CSV vers JSON, avec la sortie réellement produite
EntréeCe qui sortPourquoi
Tom,"Paris, France"Deux champs : Tom et Paris, FranceL'analyseur suit un état « entre guillemets » ; un délimiteur cité est une donnée
Tom, "Paris, France" (espace après la virgule)Trois champs : Tom, " Paris et France"RFC 4180 section 2.4 : l'espace fait partie du champ, le guillemet n'ouvre donc rien
0044 avec la conversion de types activéeLa chaîne "0044"Number("0044") s'imprime 44, ce qui n'est pas ce qui est arrivé : la cellule est laissée telle quelle
TRUE avec la conversion de types activéeLa chaîne "TRUE" ; seul true en minuscules devient un booléenLe test est une comparaison littérale avec les deux mots-clés JSON, qui sont en minuscules
name,name,name au-dessus de a,b,cLes clés name, name_2 et name_3 — les trois valeurs conservéesUne clé répétée dans un objet détruit des données : la deuxième et la troisième sont renommées
Un fichier Windows-1252 déposé sur l'outilAndr� et K�ln, analysés proprement, sans avertissementPas de sélecteur d'encodage : le lecteur suppose de l'UTF-8 et remplace chaque octet invalide
Un fichier qui commence par sep=;Le point-virgule est bien détecté, mais sep= devient la première clé et le vrai en-tête devient une ligne de donnéesL'indication est une convention Excel, absente de toute définition du CSV : l'analyseur la lit comme un enregistrement
Convertisseur CSV vers JSONAnalyse du texte CSV (avec ligne d'en-tête) en un tableau JSON d'objets, en gérant les champs entre guillemets. Dépose un fichier plutôt que de le coller : il est lu dans ton navigateur et n'est jamais envoyé.Essayer l'outil

Questions fréquentes

Faut-il activer la conversion de types ou la laisser désactivée ?
Laisse-la désactivée, sauf si quelque chose en aval réclame de vrais nombres. Une chaîne est une représentation sans perte de ce qu'il y avait dans la cellule ; un nombre est une représentation avec perte, et la perte est irréversible. Le garde-fou aller-retour fait que cet outil précis n'abîmera pas 0044, 1.0 ni un identifiant à 19 chiffres, mais il convertira une colonne de codes postaux dépourvus de zéro initial : 75001 et 75008 deviennent des nombres alors qu'un code néerlandais comme 1012 AB reste une chaîne — une colonne, deux types, et le consommateur du JSON doit gérer les deux. Si tu as besoin de nombres, convertis après coup les colonnes qui t'intéressent, où tu peux les nommer, plutôt que de laisser une heuristique trancher colonne par colonne.
Comment le convertisseur sait-il que mon fichier utilise des points-virgules ?
Il compte les délimiteurs candidats — virgule, point-virgule, tabulation et barre verticale — sur le premier enregistrement seulement, en ignorant ce qui se trouve entre guillemets, et retient le plus fréquent. Ne lire que le premier enregistrement est délibéré : un en-tête comme "Nom;Prénom" ne doit pas être noté par une virgule enfouie dans une adresse citée trois cents lignes plus bas. La limite en est le miroir. Si ton en-tête contient une virgule et que tes lignes de données utilisent des points-virgules, le détecteur choisit la virgule et chaque ligne devient un champ unique. Deux symptômes trahissent le problème immédiatement : une seule clé par objet, et une clé dont le nom est toute la ligne d'en-tête. Dans le doute, fixe le délimiteur explicitement plutôt que de compter sur la détection.
Pourquoi mes caractères accentués sont-ils devenus des points d'interrogation ou des losanges noirs ?
Parce que le fichier n'était pas en UTF-8 et que rien ne l'a dit au lecteur. Le losange noir au point d'interrogation est U+FFFD, le caractère de remplacement Unicode, et c'est ce qu'un décodeur émet quand une séquence d'octets n'est pas valide dans l'encodage qu'on lui a demandé de supposer. Ton fichier était presque à coup sûr en Windows-1252 ou ISO 8859-1, où é est l'octet unique E9 ; l'UTF-8 a besoin de deux octets pour é, et E9 seul n'est le début légal de rien. Les dégâts ont lieu avant que l'analyseur CSV ne tourne : aucun réglage CSV ne les défera. Ouvre l'original dans un éditeur qui permet de choisir l'encodage, enregistre en UTF-8, et reconvertis. Si le fichier vient d'un tableur, exporte-le avec l'option UTF-8 plutôt qu'en CSV simple.
Mes lignes n'ont pas toutes le même nombre de champs. Vais-je perdre des données ?
Non. La largeur de sortie est celle de la ligne la plus large du fichier, y compris les lignes plus larges que l'en-tête. Une ligne courte reçoit des chaînes vides pour les colonnes manquantes ; une ligne longue reçoit des clés supplémentaires nommées column4, column5, etc., pour les champs que l'en-tête n'a jamais nommés. Rien n'est tronqué, et cela compte : une ligne trop longue est en général le symptôme d'un délimiteur non échappé quelque part au-dessus, et tronquer effacerait la preuve. Si tu vois column4 dans ton JSON alors que ton en-tête n'avait que trois noms, cherche un champ contenant un délimiteur non cité — c'est là que le fichier a déraillé.
Existe-t-il une version du CSV qui n'a pas ces problèmes ?
Pas à l'intérieur du CSV lui-même, car le format n'a nulle part où loger les métadonnées qui trancheraient les questions. Ce qui existe, ce sont des conventions posées par-dessus : un fichier de schéma accompagnant qui nomme les colonnes et leurs types, un profil d'export figé convenu entre les deux systèmes, ou un format qui porte ses propres types. Si tu maîtrises les deux bouts, le JSON Lines — un objet JSON par ligne — règle d'un coup les guillemets, les sauts de ligne et les types, au prix d'un fichier plus gros qui ne s'ouvre pas dans un tableur. Si tu ne maîtrises pas les deux bouts, la réponse pratique est d'être ennuyeux : UTF-8 avec BOM, virgule ou point-virgule de façon constante, tous les champs cités, pas de ligne sep=, et un en-tête aux noms uniques et sans délimiteur.

Articles qui pourraient t'intéresser

Tous les guides
ExplicationPoint-virgule, tabulation, barre verticale : choisir un délimiteur qui survit au trajetPourquoi la langue du lecteur décide du délimiteur, ce que le convertisseur fait aux guillemets quand tu changes, ce qu'est réellement la première ligne sep=, et le nombre de cellules citées sur le même export écrit de cinq façons.ExplicationJSON vers CSV quand la structure est imbriquée : pourquoi il n'y a pas de bonne réponseLes deux mêmes commandes ressortent sur cinq colonnes d'un convertisseur et dix d'un autre, et aucun n'a tort. Chemins pointés, tableaux de scalaires, tableaux d'objets et enregistrements aux clés différentes : quatre décisions, prises à ta place, le plus souvent en silence.GuideColler un tableau dans une pull request : ce qui casse, et les deux caractères qui cassent toutUn tableau Markdown n'interdit que deux caractères dans une cellule : la barre verticale et le saut de ligne. Voici ce que fait chacun, comment un convertisseur les traite, pourquoi l'échappement doit être appliqué dans le bon ordre, et pourquoi l'alignement du source ne compte jamais.TutorielComment convertir du JSON en CSV : aplatir des tableaux d'objets en lignes et colonnesUn guide pratique pour transformer un tableau JSON d'objets en fichier CSV propre, avec l'aplatissement des champs imbriqués et la gestion des cas limites.GuideTransposer un tableau dont les lignes auraient dû être des colonnesCe qu'il advient de la ligne d'en-tête, des lignes de longueurs inégales, des types — et la seule chose pour laquelle on confond régulièrement la transposition et qu'elle ne sait pas faire.ExplicationPourquoi ton CSV casse les accents et les dates dans ExcelTrois pannes totalement différentes se cachent derrière la même phrase. L'une est l'encodage, l'autre le séparateur, la troisième Excel qui devine des types en ouvrant le fichier — et le remède diffère pour chacune. Voici comment les distinguer en cinq secondes.

Outils similaires

Ceci décrit ce que font ces convertisseurs aujourd'hui, vérifié en les exécutant, et non ce qu'une norme imposerait à un convertisseur. Le CSV n'a pas de norme prescriptive : la RFC 4180 est informative et décrit un usage courant, si bien que deux outils apparemment corrects peuvent diverger sur le même fichier sans qu'aucun ait tort. L'aplatissement, la détection de types et celle des tableaux sont des conventions, pas des règles. Avant de convertir des données que tu ne pourras pas réexporter, passe d'abord sur une copie et compare le nombre de lignes et de colonnes aux deux bouts.

Sources

Tu as repéré une erreur dans cet article ?