JSON Toolkit

Convertir JSON vers et depuis CSV et données tabulaires

L’essentiel : CSV est bidimensionnel, JSON est en forme d'arbre. La partie difficile de la conversion n'est pas le code — c'est de décider à l'avance comment les tableaux sont aplatis et comment les types sont préservés.

Débutant~10 min de lecturePublié le 2026-10-30Mis à jour le 2026-10-30
Illustration de la conversion JSON ↔ CSV et données tabulaires

L'essentiel : aplatissez avec des clés à points et des tableaux sérialisés en JSON quand des humains liront l'export, découpez en table parente plus tables liées pour charger une base, et développez les tableaux en lignes supplémentaires seulement pour des tableaux croisés ; à la restauration, convertissez selon des définitions de colonnes explicites plutôt que par inférence globale.

Aplatir un arbre en table perd toujours la hiérarchie ; restaurer une table en arbre devine toujours les types. Les deux directions exigent d'énoncer leurs règles à l'avance.

1. Ce que CSV spécifie réellement

La RFC 4180 tient en quelques pages et spécifie moins que beaucoup ne le supposent :

  1. Chaque enregistrement occupe une ligne, les champs étant séparés par des virgules ;
  2. Le terminateur de ligne est CRLF, bien qu'en pratique \n soit aussi accepté ;
  3. Un champ contenant une virgule, un guillemet double ou un retour à la ligne doit être entouré de guillemets doubles ;
  4. Les guillemets doubles à l'intérieur d'un champ s'écrivent comme deux guillemets doubles "" ;
  5. La première ligne peut être un en-tête ;
  6. Aucun encodage de caractères n'est spécifié, et les types non plus.
id,name,remark
1,张三,"喜欢 JSON,讨厌尾逗号"
2,李四,"他说:""这行有引号"""
3,王五,"多行
备注"

Faites attention à la règle 3 : les champs contenant des retours à la ligne sont légaux, donc un parseur naïf qui découpe sur \n puis sur , est garanti faux.

2. JSON → CSV : décidez d'abord la structure

Il n'existe que trois stratégies pour aplatir une structure arborescente en table bidimensionnelle :

Stratégie 1 : aplatir les champs (la plus courante)

{ "id": 1, "user": { "name": "张三", "city": "杭州" }, "tags": ["a", "b"] }
id,user.name,user.city,tags
1,张三,杭州,"[""a"",""b""]"
  • Joignez les clés d'objets imbriqués avec . ;
  • Les tableaux ne peuvent pas s'exprimer en colonnes, donc JSON.stringify-ez-les directement dans la cellule (ce qui reste un champ CSV légal) ;
  • Inconvénient : les outils de tableur ne peuvent pas filtrer le tableau, donc un second parsing est nécessaire.

Stratégie 2 : développer le tableau en plusieurs lignes

id,user.name,tag
1,张三,a
1,张三,b

Cela convient aux relations un-à-plusieurs où les consommateurs aval agrègent par élément. L'inconvénient est la duplication des champs parents et l'explosion du nombre de lignes.

Stratégie 3 : découper en plusieurs tables

Une table principale plus des tables liées jointes par un id — l'approche la plus propre, mais elle dépasse « un simple export » et ressemble davantage à de la modélisation de données.

Laquelle ? Utilisez la stratégie 1 pour les exports destinés aux humains, la stratégie 3 pour charger dans une base, et la stratégie 2 pour des tableaux croisés.

3. CSV → JSON : comment les types sont devinés

CSV n'a pas de types, donc chaque étape de restauration est une supposition :

Contenu de cellule Signification possible Risque de conversion directe en nombre
00123 Matricule, code postal Zéros initiaux perdus → 123
1.10 Numéro de version, montant Devient 1.1
1e5 Identifiant ou notation scientifique Devient 100000
2026-10-30 Date ou chaîne Ambiguïté sur le fuseau horaire et le format
13800138000 Numéro de téléphone Encore sûr à 11 chiffres, mais certains identifiants perdent vraiment de la précision
"" Chaîne vide ou null Ambiguïté sémantique

Défaut suggéré : traitez tout comme une chaîne, et laissez un schéma ou une définition de colonne explicite décider quelles colonnes deviennent des nombres ou des booléens. L'« inférence intelligente » est délicieuse à l'export et une source d'incidents à l'import.

type ColumnType = 'string' | 'number' | 'boolean' | 'json';

/**
 * Converts CSV rows into objects according to the column definitions.
 * @param rows The raw row array; the first row is the header
 * @param columnTypes Mapping from column name to type
 * @returns The converted array of objects
 */
function rowsToRecords(
  rows: string[][],
  columnTypes: Record<string, ColumnType>,
): Array<Record<string, unknown>> {
  const [headerRow, ...dataRows] = rows;

  return dataRows.map((dataRow) => {
    const record: Record<string, unknown> = {};

    headerRow.forEach((columnName, columnIndex) => {
      const rawValue = dataRow[columnIndex] ?? '';
      const columnType = columnTypes[columnName] ?? 'string';

      if (columnType === 'number') {
        const numericValue = Number(rawValue);
        record[columnName] = Number.isFinite(numericValue) ? numericValue : null;
        return;
      }
      if (columnType === 'boolean') {
        record[columnName] = rawValue === 'true' || rawValue === '1';
        return;
      }
      if (columnType === 'json') {
        try {
          record[columnName] = JSON.parse(rawValue);
        } catch {
          record[columnName] = rawValue;
        }
        return;
      }
      record[columnName] = rawValue;
    });

    return record;
  });
}

4. Trois pièges autour d'Excel

4.1 Encodage

Quand Excel ouvre un CSV UTF-8 sans BOM, le texte chinois devient du mojibake. Ajouter un BOM (\uFEFF) à l'export est la correction de compatibilité la moins coûteuse :

const bom = '\uFEFF';
const csvContent = `${bom}${headerLine}\n${bodyLines.join('\n')}`;

4.2 Les longs nombres qui deviennent de la notation scientifique

Un numéro d'identité nationale de 18 chiffres ou un numéro de commande de 19 chiffres s'affiche dans Excel comme 1.23457E+17, et la valeur elle-même a déjà été tronquée. Préfixer le champ par une tabulation ou l'envelopper comme formule (="1234...") préserve le format texte, mais transforme la cellule en formule. L'approche plus sûre est de dire aux utilisateurs « utilisez la fonction d'import et spécifiez la colonne comme texte », ou de proposer un .xlsx en plus de l'export.

4.3 Délimiteurs affectés par la locale

Certaines locales européennes utilisent par défaut le point-virgule ; comme séparateur. Lors d'une distribution entre régions, soit fixez le CSV sur des virgules et précisez-le, soit proposez une version TSV (séparée par tabulations) — TSV tolère mieux les locales.

5. Traiter les grands fichiers

Ne chargez pas en mémoire d'un coup des CSV de plus de cent mille lignes :

import { createReadStream } from 'node:fs';
import { parse } from 'csv-parse';

const parser = createReadStream('orders.csv').pipe(
  parse({ columns: true, skipEmptyLines: true, bom: true }),
);

for await (const record of parser) {
  // Process one record at a time; memory usage is independent of file size
}

La direction export fonctionne de la même manière : générez et écrivez dans un flux au fur et à mesure, plutôt que de concaténer une chaîne géante.

6. Auto-vérification de la cohérence aller-retour

Un aller-retour CSV (JSON → CSV → JSON) n'est presque jamais exactement identique, mais vous pouvez définir quelles pertes sont acceptables :

Vérification Attente
Nombre de champs Identique
Valeurs de chaîne Identiques (y compris les zéros initiaux)
Types numériques Déterminés par la définition de colonne, pas par inférence
Objets imbriqués Récupérables via les chemins à points
Tableaux Visibles après sérialisation en chaîne JSON
Valeurs vides Normalisées en null ou en chaîne vide — choisissez exactement l'une

Mettre ce tableau dans des tests est bien plus efficace qu'un accord verbal.

7. Liste de vérification

  • [ ] La sémantique « une ligne est un enregistrement » est énoncée explicitement et reflétée dans le nom de fichier d'export ;
  • [ ] Chaque colonne susceptible de perdre de la précision est exportée en chaîne ;
  • [ ] Les CSV chinois incluent un BOM, ou l'encodage est précisé explicitement ;
  • [ ] Les champs contenant des virgules, guillemets ou retours à la ligne sont correctement échappés (utilisez une bibliothèque, ne concaténez pas des chaînes vous-même) ;
  • [ ] Les grands fichiers passent par des lectures et écritures en flux ;
  • [ ] Les imports convertissent les types selon la définition de colonne, jamais par inférence intelligente globale.

FAQ

Comment choisir entre aplatir, développer les lignes et découper les tables ?

Aplatissez pour les exports destinés aux humains : joignez les clés imbriquées avec . et JSON.stringify-ez les tableaux dans la cellule, au prix de ne pas pouvoir filtrer ces tableaux dans un tableur. Découpez en table parente plus tables liées quand la cible est une base. Développez les tableaux en lignes supplémentaires quand les consommateurs aval agrègent par élément, en acceptant la duplication des champs parents.

Pourquoi un import CSV ne devrait-il pas inférer les types automatiquement ?

La RFC 4180 spécifie les délimiteurs et l'échappement mais pas les types, donc chaque étape de restauration est une supposition : 00123 perd ses zéros initiaux, 1.10 devient 1.1, et 1e5 devient 100000. Traitez tout comme une chaîne par défaut et laissez un schéma ou une définition de colonne explicite décider quelles colonnes deviennent des nombres ou des booléens.

Faut-il s'attendre à ce qu'un aller-retour CSV reproduise exactement le JSON original ?

Presque jamais. Définissez plutôt les pertes acceptables : nombre de champs identique, valeurs de chaîne identiques y compris les zéros initiaux, types numériques décidés par la définition de colonne, objets imbriqués récupérables via les chemins à points, tableaux visibles après sérialisation, et valeurs vides normalisées en null ou en chaîne vide — choisissez exactement l'une.