Aller au contenu
Réussir mon BTS SIO

Tape une notion, une commande ou le nom d'une épreuve.

Par exemple

Chapitre 521 minIntermédiaire

JSON, XML et CSV : les formats d'échange de données

Par Josselyn C., prof de BTS SIO. Mis à jour le .

À la fin de ce chapitre, tu sauras

  • écrire et lire un document JSON valide ;
  • éviter les pièges des dates, des montants et des grands entiers ;
  • valider un document avec JSON Schema ;
  • lire du XML et produire un CSV correct, et choisir le bon format.

5 exercices corrigés

JSON (JavaScript Object Notation) est un format texte qui représente des données structurées avec des objets entre accolades, des tableaux entre crochets et quatre types de valeurs : chaînes, nombres, booléens et null. C'est le format de presque toutes les API REST actuelles. Ce chapitre détaille sa syntaxe et ses pièges (dates, montants, grands entiers), la validation avec JSON Schema, puis XML et CSV, toujours présents dans les échanges entre entreprises et les exports.

La syntaxe de JSON

JSON est normalisé par la RFC 8259 et par la norme ECMA-404. Sa grammaire tient en quelques lignes :

  • un objet est une liste de paires "clé": valeur entre accolades, séparées par des virgules ;
  • un tableau est une liste ordonnée de valeurs entre crochets ;
  • une valeur est une chaîne (entre guillemets doubles), un nombre, true, false, null, un objet ou un tableau.

Voici la fiche de frais complète telle que la renvoie GET /v1/fiches/12 quand on demande le détail :

json
{
  "id": 12,
  "mois": "2026-10",
  "etat": "CR",
  "dateModification": "2026-10-28T17:42:05+01:00",
  "visiteur": {
    "id": 7,
    "nom": "Martin",
    "prenom": "Lucas"
  },
  "lignesForfait": [
    { "code": "ETP", "libelle": "Forfait étape", "quantite": 3, "montantUnitaire": 110.00 },
    { "code": "KM", "libelle": "Frais kilométriques", "quantite": 420, "montantUnitaire": 0.62 },
    { "code": "NUI", "libelle": "Nuitée hôtel", "quantite": 2, "montantUnitaire": 80.00 },
    { "code": "REP", "libelle": "Repas restaurant", "quantite": 5, "montantUnitaire": 25.00 }
  ],
  "fraisHorsForfait": [
    { "id": 58, "date": "2026-10-14", "libelle": "Repas client à Lyon", "montant": 23.50, "justificatif": true }
  ],
  "montantValide": null
}

Pour lire une valeur, on suit le chemin depuis la racine : fiche.visiteur.nom vaut "Martin", fiche.lignesForfait[1].quantite vaut 420 (les tableaux commencent à l'indice 0), fiche.fraisHorsForfait[0].montant vaut 23.5.

Les types JSON dans chaque langage

JSONJavaScriptPHP (json_decode($s, true))PythonC# (System.Text.Json)
objet {}Objecttableau associatifdictclasse ou JsonObject
tableau []Arraytableau indexélistList<T>, tableau
chaînestringstringstrstring
nombre entiernumberintintint, long
nombre décimalnumberfloatfloatdouble, decimal
true, falsebooleanboolTrue, Falsebool
nullnullnullNonenull

JSON n'a pas de type date, ni de type décimal exact, ni de distinction entre entiers et décimaux. Ces trois manques causent l'essentiel des bogues d'échange.

Les dates : toujours en ISO 8601

Une date s'écrit dans une chaîne, au format ISO 8601 (plus précisément son profil RFC 3339) : "2026-10-14" pour une date, "2026-10-28T17:42:05+01:00" pour un instant, avec le décalage horaire. Z à la fin signifie UTC : "2026-10-28T16:42:05Z" désigne le même instant.

  • Jamais de format local : "14/10/2026" est ambigu (le 10 octobre en anglais américain s'écrit 10/14/2026) et ne se trie pas.
  • Un instant sans fuseau ("2026-10-28T17:42:05") est interprété différemment selon le serveur et le client. Envoie le décalage ou de l'UTC.
  • Un mois seul s'écrit "2026-10", comme le champ mois d'AppliFrais.

En JavaScript, JSON.stringify convertit un objet Date en UTC : new Date("2026-10-14T09:30:00+02:00") devient "2026-10-14T07:30:00.000Z". C'est le même instant, écrit autrement.

Les montants : attention aux décimaux

Les nombres décimaux de JSON sont lus comme des nombres à virgule flottante binaires (le format IEEE 754 sur 64 bits), qui ne représentent pas exactement la plupart des fractions décimales :

js
console.log(0.1 + 0.2);          // 0.30000000000000004
console.log(0.1 + 0.2 === 0.3);  // false

Pour une fiche de frais, quelques centimes d'écart à force d'additions, c'est une anomalie comptable. Trois stratégies :

StratégieExempleAvantageInconvénient
Nombre décimal, calculs côté serveur en type exact"montant": 23.50Lisible, le plus courantLe client ne doit pas refaire les calculs en flottant
Entier en centimes"montantCentimes": 2350Aucun arrondi possibleIl faut diviser par 100 à l'affichage
Chaîne décimale"montant": "23.50"Précision conservée telle quelleIl faut la convertir avant tout calcul

Côté serveur, les calculs se font dans un type exact : DECIMAL(10,2) dans MySQL, decimal en C#, Decimal en Python, ou des entiers en centimes en JavaScript et en PHP. Le total des frais forfaitisés de la fiche 12 vaut 3 × 110 + 420 × 0,62 + 2 × 80 + 5 × 25 = 330 + 260,40 + 160 + 125 = 875,40 €.

Les grands entiers et les identifiants

En JavaScript, un number ne représente exactement les entiers que jusqu'à 2⁵³ − 1, soit 9 007 199 254 740 991 (Number.MAX_SAFE_INTEGER). Au-delà, la valeur est arrondie sans erreur :

js
JSON.parse('{"id": 9007199254740993}').id;   // 9007199254740992 : le dernier chiffre a changé

Un identifiant de 64 bits généré par certaines bases ou par des services comme X (anciennement Twitter) dépasse cette limite. La parade : envoyer les identifiants comme des chaînes ("id": "9007199254740993"). Un identifiant ne sert jamais à calculer : rien ne justifie d'en faire un nombre.

Absent, null ou vide ?

Trois façons de dire « pas de valeur », qui n'ont pas le même sens :

  • "montantValide": null : le champ existe et n'a pas de valeur (la fiche n'est pas encore validée) ;
  • le champ absent du document : l'API ne le renvoie pas (droits insuffisants, champ facultatif non demandé), ou, dans un PATCH, le client ne veut pas le modifier ;
  • "commentaire": "" : une chaîne vide, qui est une valeur.

Documente ton choix et tiens-le dans toute l'API. Pour PATCH avec JSON Merge Patch, la règle est fixée par la RFC 7396 : absent signifie « ne pas toucher », null signifie « supprimer ».

Nommer les champs

Choisis une convention et ne la change plus : camelCase (montantValide, le plus courant en JSON, naturel en JavaScript et en C#) ou snake_case (montant_valide, fréquent dans les API écrites en Python ou en Ruby). Ce cours utilise camelCase. Évite les abréviations obscures (mtVal), les accents dans les clés ("libellé" est valide mais source d'ennuis), et les clés qui changent de type d'une réponse à l'autre.

Sérialiser et désérialiser

Sérialiser, c'est transformer un objet du langage en texte JSON pour l'envoyer ; désérialiser, c'est l'inverse. Chaque langage a ses fonctions.

js
// JavaScript
const texte = JSON.stringify({ mois: '2026-10', montant: 23.50, commentaire: null });
// '{"mois":"2026-10","montant":23.5,"commentaire":null}'
const objet = JSON.parse(texte);
console.log(objet.montant);   // 23.5

JSON.stringify écrit 23.50 comme 23.5 : un nombre n'a pas de zéros de remplissage. Il ignore les propriétés undefined et les fonctions. JSON.parse lève une SyntaxError sur un document invalide : entoure-le d'un try … catch quand le texte vient de l'extérieur.

php
<?php
// PHP
$texte = json_encode(['libelle' => 'Repas client à Lyon', 'montant' => 23.5],
    JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);
// {"libelle":"Repas client à Lyon","montant":23.5}

$donnees = json_decode($texte, true, 512, JSON_THROW_ON_ERROR);  // true : tableau associatif
echo $donnees['libelle'];

Sans JSON_UNESCAPED_UNICODE, PHP écrit à sous la forme \u00e0 : c'est du JSON valide, juste moins lisible. Sans JSON_THROW_ON_ERROR, json_decode renvoie null en silence sur un texte invalide, et ton code continue avec une variable vide.

python
# Python
import json
texte = json.dumps({"libelle": "Repas client à Lyon", "montant": 23.5}, ensure_ascii=False)
donnees = json.loads(texte)   # lève json.JSONDecodeError si le texte est invalide

Valider un document avec JSON Schema

Un document JSON bien formé peut contenir n'importe quoi : un montant négatif, une date au mauvais format, un champ en trop. JSON Schema est un langage, lui-même écrit en JSON, qui décrit la forme attendue d'un document. La version actuelle est la 2020-12, que reprend OpenAPI 3.1 (chapitre 7). Le schéma d'un frais hors forfait envoyé par le client :

json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "required": ["date", "libelle", "montant"],
  "properties": {
    "date": { "type": "string", "format": "date" },
    "libelle": { "type": "string", "minLength": 3, "maxLength": 100 },
    "montant": { "type": "number", "exclusiveMinimum": 0, "maximum": 5000, "multipleOf": 0.01 }
  },
  "additionalProperties": false
}

Ce schéma exige trois champs, impose une date au format AAAA-MM-JJ, un libellé de 3 à 100 caractères et un montant strictement positif, d'au plus 5 000 €, au centime près. additionalProperties: false refuse tout champ non prévu : un client qui envoie "rembourse": true voit sa requête rejetée au lieu de modifier un champ qu'il ne devrait pas toucher (l'affectation de masse, chapitre 10). Des bibliothèques appliquent le schéma automatiquement : Ajv en JavaScript, opis/json-schema en PHP, jsonschema en Python.

XML : le format des échanges entre entreprises

XML (eXtensible Markup Language) structure les données avec des balises ouvrantes et fermantes, comme HTML, mais avec des noms libres. La même fiche, réduite à l'essentiel :

xml
<?xml version="1.0" encoding="UTF-8"?>
<fiche id="12" etat="CR">
  <mois>2026-10</mois>
  <visiteur id="7">
    <nom>Martin</nom>
    <prenom>Lucas</prenom>
  </visiteur>
  <fraisHorsForfait>
    <frais id="58" justificatif="true">
      <date>2026-10-14</date>
      <libelle>Repas client à Lyon</libelle>
      <montant>23.50</montant>
    </frais>
  </fraisHorsForfait>
</fiche>

Le vocabulaire à connaître :

  • le prologue <?xml … ?> déclare la version et l'encodage ;
  • un élément va d'une balise ouvrante à sa balise fermante (<mois>2026-10</mois>) ; il y a un seul élément racine (<fiche>) ;
  • un attribut est une paire nom="valeur" dans la balise ouvrante (id="12") ;
  • un document est bien formé s'il respecte la syntaxe (chaque balise est fermée, les balises sont correctement imbriquées, les attributs sont entre guillemets) ;
  • il est valide s'il respecte en plus un schéma : une DTD ou, plus souvent, un schéma XSD ;
  • les espaces de noms (xmlns) évitent les collisions quand un document mélange plusieurs vocabulaires ;
  • XPath désigne des parties du document : /fiche/visiteur/nom sélectionne <nom>Martin</nom>.

Les caractères < et & doivent s'écrire &lt; et &amp; dans le texte. En XML, tout est texte : 23.50 n'est un nombre que si le schéma le dit.

XML reste très présent hors des API REST : les services web SOAP, les flux RSS, les plans de site (sitemap.xml), les fichiers Office (un .docx est une archive de fichiers XML), la configuration Java et .NET, et les échanges normalisés entre entreprises. La réforme française de la facturation électronique en est un bon exemple : depuis le 1er septembre 2026, toute entreprise assujettie à la TVA doit pouvoir recevoir des factures électroniques, dans l'un des trois formats admis (Factur-X, UBL ou CII), tous construits sur XML.

JSON ou XML ?

CritèreJSONXML
LisibilitéConcisVerbeux (chaque nom est écrit deux fois)
TypesChaîne, nombre, booléen, null, objet, tableauTout est texte ; les types viennent du schéma
SchémaJSON Schema (facultatif)DTD, XSD (très utilisés)
MétadonnéesPas d'attributsAttributs, espaces de noms, commentaires
Dans le navigateurNatif (JSON.parse, response.json())DOMParser, plus lourd
Usages typiquesAPI REST, configuration (package.json), bases documentsSOAP, échanges B2B normalisés, documents, facturation électronique

Pour une nouvelle API REST, choisis JSON. Sache lire et produire du XML pour dialoguer avec un système existant.

CSV : le format des exports

CSV (Comma-Separated Values) représente un tableau : une ligne par enregistrement, des valeurs séparées par un délimiteur, et souvent une première ligne d'en-têtes. La RFC 4180 décrit le format de référence. Une API l'utilise pour les exports (le service comptable veut ouvrir les fiches validées dans un tableur), avec le type text/csv :

text
id;visiteur;mois;etat;montantValide
10;Martin Lucas;2026-08;MP;412,60
11;Martin Lucas;2026-09;VA;389,20
14;"Benali; Sofiane";2026-09;VA;1 024,00

Les pièges de CSV :

  • le séparateur : la norme prévoit la virgule, mais Excel en français attend le point-virgule, parce que la virgule y sert de séparateur décimal. Un export destiné à un tableur français utilise souvent ; et des montants avec une virgule ; un export destiné à un programme utilise , et le point décimal. Documente ton choix ;
  • les guillemets : une valeur qui contient le séparateur, un guillemet ou un saut de ligne doit être entourée de guillemets doubles ("Benali; Sofiane"), et un guillemet à l'intérieur se double ("") ;
  • l'encodage : écris en UTF-8. Pour qu'Excel affiche correctement les accents, beaucoup d'exports ajoutent au début du fichier la marque d'ordre d'octets (BOM) UTF-8 ;
  • pas de types, pas de hiérarchie : impossible de représenter les frais d'une fiche dans une même ligne sans bricolage. CSV convient aux données plates ;
  • l'injection de formules : une cellule qui commence par =, +, - ou @ peut être exécutée comme une formule par le tableur. Un export qui contient des saisies d'utilisateurs doit neutraliser ces caractères, par exemple en préfixant la cellule d'une apostrophe.

Et YAML ?

YAML est un format de données lisible, fondé sur l'indentation plutôt que sur des accolades. Une API ne l'utilise presque jamais pour ses échanges, mais tu le rencontres partout autour : les fichiers OpenAPI (chapitre 7), docker-compose.yml, les pipelines d'intégration continue. Tout document JSON peut s'écrire en YAML, et réciproquement (sauf les commentaires, que seul YAML accepte).

Exercices

Exercice 1 sur 5 Trouver les erreurs Réussi À revoir

Ce document est refusé par JSON.parse. Trouve les six erreurs et réécris-le correctement.

text
{
  'id': 58,
  libelle: "Repas client",
  "montant": 23,50,
  "justificatif": undefined,
  "date": "2026-10-14",   // date du repas
}
Voir la réponseMasquer la réponse de l'exercice 1
  1. 'id' : les clés exigent des guillemets doubles.
  2. libelle : clé sans guillemets.
  3. 23,50 : le séparateur décimal de JSON est le point. Telle quelle, la virgule sépare deux valeurs, ce qui casse la syntaxe.
  4. undefined n'existe pas en JSON : null, false ou supprimer la ligne.
  5. Le commentaire // date du repas : JSON n'accepte aucun commentaire.
  6. La virgule finale après la dernière valeur.
json
{
  "id": 58,
  "libelle": "Repas client",
  "montant": 23.50,
  "justificatif": false,
  "date": "2026-10-14"
}

Tu avais trouvé ?

Exercice 2 sur 5 Suivre un chemin dans un document Réussi À revoir

À partir du document complet de la fiche 12 donné au début du chapitre, écris l'expression JavaScript qui renvoie :

  1. le prénom du visiteur ;
  2. le nombre de kilomètres ;
  3. le libellé du premier frais hors forfait ;
  4. le nombre de lignes de frais forfaitisés ;
  5. le total des frais forfaitisés, avec reduce.
Voir la réponseMasquer la réponse de l'exercice 2
  1. fiche.visiteur.prenom, qui vaut "Lucas".
  2. fiche.lignesForfait[1].quantite, qui vaut 420 ; plus robuste : fiche.lignesForfait.find(l => l.code === 'KM').quantite, qui ne dépend pas de l'ordre des lignes.
  3. fiche.fraisHorsForfait[0].libelle, qui vaut "Repas client à Lyon".
  4. fiche.lignesForfait.length, qui vaut 4.
  5. fiche.lignesForfait.reduce((total, l) => total + l.quantite * l.montantUnitaire, 0), qui donne 875.4. Pour un montant affiché, arrondis au centime ; pour un montant enregistré, fais le calcul côté serveur dans un type exact.

Tu avais trouvé ?

Exercice 3 sur 5 De XML à JSON Réussi À revoir

Traduis ce document XML en JSON, en choisissant des types adaptés.

xml
<visiteur id="7" actif="true">
  <nom>Martin</nom>
  <prenom>Lucas</prenom>
  <dateEmbauche>2021-09-01</dateEmbauche>
  <regions>
    <region>Rhône</region>
    <region>Isère</region>
  </regions>
</visiteur>
Voir la réponseMasquer la réponse de l'exercice 3
json
{
  "id": 7,
  "actif": true,
  "nom": "Martin",
  "prenom": "Lucas",
  "dateEmbauche": "2021-09-01",
  "regions": ["Rhône", "Isère"]
}

Les attributs deviennent des champs comme les autres ; id devient un nombre et actif un booléen (en XML, tout était texte). Les éléments répétés <region> deviennent un tableau, et la date reste une chaîne ISO 8601.

Tu avais trouvé ?

Exercice 4 sur 5 Choisir le format Réussi À revoir

Quel format choisis-tu pour chaque échange ? Justifie.

  1. L'API d'AppliFrais renvoie les fiches au front React.
  2. Le service comptable veut ouvrir dans Excel la liste des fiches validées du mois.
  3. AppliFrais doit transmettre les factures d'un fournisseur à une plateforme de facturation électronique.
  4. Le fichier qui décrit les routes de l'API pour la documentation.
Voir la réponseMasquer la réponse de l'exercice 4
  1. JSON : natif en JavaScript, léger, typé.
  2. CSV avec point-virgule et montants à virgule (ou un vrai fichier .xlsx) : des données plates, pour un tableur français.
  3. XML, dans l'un des formats imposés par la réforme (UBL, CII, ou Factur-X qui associe un PDF et un XML) : c'est le format attendu, on ne le choisit pas.
  4. YAML (ou JSON) au format OpenAPI : YAML est plus lisible pour un fichier écrit à la main et accepte les commentaires.

Tu avais trouvé ?

Exercice 5 sur 5 Le centime perdu Réussi À revoir

Le front additionne en JavaScript trois frais hors forfait reçus de l'API, un parking à 3.10, un péage à 12.20 et un café à 4.30, puis compare le total au montant 19.60 renvoyé par le serveur avec ===.

  1. Le test est-il vrai ? Vérifie-le dans la console de ton navigateur.
  2. Pourquoi ?
  3. Propose deux corrections.
Voir la réponseMasquer la réponse de l'exercice 5
  1. Non. 3.10 + 12.20 + 4.30 donne 19.599999999999998, donc 3.10 + 12.20 + 4.30 === 19.60 renvoie false. Le front signalerait une incohérence qui n'existe pas.
  2. Les nombres sont stockés en virgule flottante binaire, qui ne représente pas exactement 3,10, 12,20 ni 4,30 ; les erreurs d'arrondi s'accumulent au fil des additions.
  3. Calculer en centimes entiers (310 + 1220 + 430 === 1960 est vrai), ou ne pas refaire le calcul côté client et afficher le total calculé par le serveur dans un type exact. Pour une simple comparaison, on peut aussi comparer des valeurs arrondies au centime : Math.round(total * 100) === Math.round(19.60 * 100). À l'affichage, total.toFixed(2) donne bien "19.60".

Tu avais trouvé ?

Chapitre terminé !

Fais les 5 exercices avant de passer à la suite : c'est en t'entraînant que tout se fixe.

chapitres
–
de série
–
du cours
–
Chapitre suivant : Concevoir une API REST : pagination, filtres, versionnement, erreursRevoir le sommaire du cours