La nuit, les données circulent. D'un service à l'autre. Comme des messages codés laissés dans des boîtes aux lettres. JSON est l'encre. HTTP est la poste. Fetch est le coursier.
JSON - Le Langage Universel
Qu'est-ce que JSON ?
JavaScript Object Notation.
Un format texte. Léger. Lisible. Universel.
Le lingua franca du web.
// En mémoire, c'est un objet JavaScript
const detective = {
nom: "Marlowe",
prenom: "Philip",
age: 42,
actif: true,
cas_resolus: 147,
specialites: ["surveillance", "interrogatoire", "filature"],
bureau: {
adresse: "615 Cahuenga Blvd",
ville: "Los Angeles",
etage: 3
},
dernier_cas: null
};
// Sérialisé en JSON, c'est une chaîne
const jsonString = JSON.stringify(detective);
console.log(jsonString);
// {
// "nom": "Marlowe",
// "prenom": "Philip",
// "age": 42,
// "actif": true,
// "cas_resolus": 147,
// "specialites": ["surveillance", "interrogatoire", "filature"],
// "bureau": {
// "adresse": "615 Cahuenga Blvd",
// "ville": "Los Angeles",
// "etage": 3
// },
// "dernier_cas": null
// }
Les Règles du Jeu
Syntaxe stricte :
- Les clés toujours entre doubles guillemets
- Chaînes entre doubles guillemets
- Pas de commentaires
- Pas de trailing commas
- Pas de fonctions, dates, undefined
// VALIDE
const valide = {
"nom": "Marlowe",
"age": 42,
"enquete": true,
"temoins": ["Témoin A", "Témoin B"],
"details": null
};
// INVALIDE
const invalide = {
nom: "Marlowe", // clés sans guillemets (OK en JS, pas en JSON pur)
age: undefined, // undefined n'existe pas en JSON
date: new Date(), // Date devient une chaîne
fn: function() {} // Les fonctions disparaissent
};
console.log(JSON.stringify(invalide));
// {"nom":"Marlowe","date":"2024-03-18T10:30:00.000Z"}
// age et fn ont disparu
JSON vs JavaScript Object
const obj = {
name: "Marlowe",
age: 42,
// Méthode (ne sera pas dans JSON)
sePresenter() {
return `Je suis ${this.name}`;
}
};
// Sérialisation
const json = JSON.stringify(obj);
console.log(json); // {"name":"Marlowe","age":42}
// Désérialisation
const obj2 = JSON.parse(json);
console.log(obj2.sePresenter); // undefined (perdu dans la sérialisation)
Méthodes JSON
JSON.stringify(value[, replacer[, space]])
const detective = {
nom: "Marlowe",
age: 42,
dossier_noir: true,
notes: "À surveiller"
};
// Simple
console.log(JSON.stringify(detective));
// {"nom":"Marlowe","age":42,"dossier_noir":true,"notes":"À surveiller"}
// Avec replacer (fonction)
const censure = JSON.stringify(detective, (key, value) => {
if (key === 'notes') return 'CLASSIFIED';
if (key === 'dossier_noir') return undefined; // Supprimé
return value;
});
console.log(censure);
// {"nom":"Marlowe","age":42,"notes":"CLASSIFIED"}
// Avec replacer (tableau)
const partiel = JSON.stringify(detective, ['nom', 'age']);
console.log(partiel); // {"nom":"Marlowe","age":42}
// Avec indentation (pretty print)
const joli = JSON.stringify(detective, null, 2);
console.log(joli);
// {
// "nom": "Marlowe",
// "age": 42,
// "dossier_noir": true,
// "notes": "À surveiller"
// }
JSON.parse(text[, reviver])
const json = '{"nom":"Marlowe","age":42,"date_embauche":"2005-03-15T10:30:00.000Z"}';
// Simple
const obj = JSON.parse(json);
console.log(obj.date_embauche); // "2005-03-15T10:30:00.000Z" (string)
// Avec reviver pour transformer
const obj2 = JSON.parse(json, (key, value) => {
if (key === 'date_embauche') return new Date(value);
return value;
});
console.log(obj2.date_embauche); // Date object
console.log(obj2.date_embauche.getFullYear()); // 2005
Cas d'Usage Réels
Configuration :
// config.json
{
"api": {
"baseUrl": "https://api.surveillance.local",
"timeout": 5000,
"retries": 3
},
"database": {
"host": "localhost",
"port": 5432,
"name": "surveillance_db"
},
"features": {
"analytics": true,
"notifications": false
}
}
// Chargement
const config = JSON.parse(fs.readFileSync('config.json', 'utf8'));
Échange entre services :
// Données d'un témoin
const temoin = {
id: "TEM-2024-001",
nom: "Smith",
prenom: "John",
deposition: "J'ai vu une voiture noire...",
date_deposition: new Date().toISOString(),
fichiers_joints: ["photo1.jpg", "enregistrement.mp3"]
};
// Envoi à l'API
const jsonTemoin = JSON.stringify(temoin);
// POST /api/temoins avec jsonTemoin dans le body
Fetch API - Le Coursier de la Nuit
Introduction à Fetch
Avant Fetch : XMLHttpRequest. Lourd. Verbose.
Fetch : moderne, basé sur Promises, simple.
// La syntaxe de base
fetch(url, options)
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Erreur:', error));
Requête GET - Demander des Informations
// GET simple
fetch('https://api.surveillance.local/cas/123')
.then(response => response.json())
.then(cas => {
console.log(`Cas ${cas.id}: ${cas.titre}`);
console.log(`Statut: ${cas.statut}`);
console.log(`Témoins: ${cas.temoins.length}`);
})
.catch(err => console.error('Erreur de réseau:', err));
// Avec async/await (plus lisible)
async function getCas(id) {
try {
const response = await fetch(`https://api.surveillance.local/cas/${id}`);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const cas = await response.json();
return cas;
} catch (error) {
console.error('Erreur:', error);
return null;
}
}
// Utilisation
const cas123 = await getCas(123);
Headers - Les En-têtes du Message
// Headers simples
fetch('https://api.surveillance.local/cas', {
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
'X-API-Key': 'surveillance-2024-secret'
}
});
// Headers avec objet Headers
const headers = new Headers();
headers.append('Content-Type', 'application/json');
headers.append('Authorization', 'Bearer token123');
// Ou en une ligne
const headers = new Headers({
'Content-Type': 'application/json',
'Authorization': 'Bearer token123'
});
fetch(url, { headers });
Requête POST - Envoyer des Données
// Nouveau témoin
const nouveauTemoin = {
nom: "Dubois",
prenom: "Marie",
telephone: "+33123456789",
deposition: "J'étais au café à 21h...",
confidential: true
};
async function ajouterTemoin(temoin) {
const response = await fetch('https://api.surveillance.local/temoins', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${localStorage.getItem('token')}`
},
body: JSON.stringify(temoin)
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.message || 'Erreur lors de l\'ajout');
}
return await response.json();
}
// Utilisation
try {
const temoinCree = await ajouterTemoin(nouveauTemoin);
console.log(`Témoin ${temoinCree.id} créé avec succès`);
} catch (error) {
console.error('Échec:', error.message);
}
PUT et PATCH - Mettre à Jour
// PUT (remplacement complet)
async function mettreAJourCas(id, donnees) {
const response = await fetch(`https://api.surveillance.local/cas/${id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${getToken()}`
},
body: JSON.stringify(donnees)
});
return response.json();
}
// PATCH (mise à jour partielle)
async function mettreAJourStatut(id, nouveauStatut) {
const response = await fetch(`https://api.surveillance.local/cas/${id}/statut`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ statut: nouveauStatut })
});
return response.json();
}
DELETE - Supprimer
async function supprimerCas(id) {
const response = await fetch(`https://api.surveillance.local/cas/${id}`, {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${getToken()}`
}
});
if (!response.ok) {
throw new Error(`Échec de suppression: ${response.status}`);
}
// DELETE retourne souvent 204 No Content
if (response.status === 204) {
return { success: true, message: 'Cas supprimé' };
}
return response.json();
}
// Avec confirmation
async function supprimerAvecConfirmation(id) {
const confirmer = window.confirm('Supprimer ce cas ? Cette action est irréversible.');
if (!confirmer) {
return { cancelled: true };
}
try {
const result = await supprimerCas(id);
alert('Cas supprimé avec succès');
return result;
} catch (error) {
alert(`Erreur: ${error.message}`);
throw error;
}
}
Gestion des Réponses
async function traiterReponse(response) {
const contentType = response.headers.get('content-type');
// Différents types de réponse
if (contentType && contentType.includes('application/json')) {
return await response.json();
} else if (contentType && contentType.includes('text/')) {
return await response.text();
} else if (contentType && contentType.includes('image/')) {
return await response.blob();
} else {
return await response.arrayBuffer();
}
}
// Vérifier le statut
async function fetchAvecControle(url, options = {}) {
const response = await fetch(url, options);
if (response.status === 401) {
// Non autorisé - rediriger vers login
window.location.href = '/login';
throw new Error('Session expirée');
}
if (response.status === 403) {
// Interdit
throw new Error('Accès refusé');
}
if (response.status === 404) {
// Non trouvé
throw new Error('Ressource non trouvée');
}
if (response.status === 429) {
// Trop de requêtes
const retryAfter = response.headers.get('Retry-After') || 60;
throw new Error(`Trop de requêtes. Réessayez dans ${retryAfter} secondes.`);
}
if (!response.ok) {
const error = await response.text();
throw new Error(`HTTP ${response.status}: ${error}`);
}
return response;
}
Options Avancées
Mode CORS :
fetch('https://api.externe.com/data', {
mode: 'cors', // Défaut pour les URLs externes
credentials: 'include' // Inclure les cookies
});
Timeout manuel :
function fetchAvecTimeout(url, options = {}, timeout = 10000) {
return Promise.race([
fetch(url, options),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), timeout)
)
]);
}
Upload de fichier :
async function uploaderPreuve(casId, fichier) {
const formData = new FormData();
formData.append('preuve', fichier);
formData.append('cas_id', casId);
formData.append('description', 'Photo de la scène');
const response = await fetch('https://api.surveillance.local/preuves/upload', {
method: 'POST',
body: formData
// Pas de Content-Type header pour FormData
});
return response.json();
}
// Utilisation
const inputFichier = document.querySelector('#preuve-input');
inputFichier.addEventListener('change', async (event) => {
const fichier = event.target.files[0];
if (fichier) {
const result = await uploaderPreuve(123, fichier);
console.log('Fichier uploadé:', result);
}
});
Streaming de réponse :
// Pour les grosses réponses
async function lireFluxCas() {
const response = await fetch('https://api.surveillance.local/cas/stream');
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
const lignes = chunk.split('\n');
for (const ligne of lignes) {
if (ligne.trim()) {
try {
const cas = JSON.parse(ligne);
afficherCasEnTempsReel(cas);
} catch (e) {
// Ignorer les lignes incomplètes
}
}
}
}
}
Gestion d'Erreurs Complète
class APIError extends Error {
constructor(message, status, data) {
super(message);
this.name = 'APIError';
this.status = status;
this.data = data;
}
}
class APIClient {
constructor(baseURL, options = {}) {
this.baseURL = baseURL;
this.defaultHeaders = {
'Content-Type': 'application/json',
...options.headers
};
}
async request(endpoint, options = {}) {
const url = `${this.baseURL}${endpoint}`;
const headers = {
...this.defaultHeaders,
...options.headers
};
const config = {
...options,
headers
};
try {
const response = await fetch(url, config);
// Gérer les réponses d'erreur
if (!response.ok) {
let errorData;
try {
errorData = await response.json();
} catch {
errorData = { message: await response.text() };
}
throw new APIError(
errorData.message || `HTTP ${response.status}`,
response.status,
errorData
);
}
// Gérer les réponses vides (204 No Content)
if (response.status === 204) {
return null;
}
// Détecter le type de contenu
const contentType = response.headers.get('content-type');
if (contentType && contentType.includes('application/json')) {
return await response.json();
} else {
return await response.text();
}
} catch (error) {
// Distinguer les erreurs réseau des erreurs API
if (error instanceof APIError) {
throw error;
} else {
throw new APIError(
`Erreur réseau: ${error.message}`,
0,
{ originalError: error.message }
);
}
}
}
get(endpoint, options = {}) {
return this.request(endpoint, { ...options, method: 'GET' });
}
post(endpoint, data, options = {}) {
return this.request(endpoint, {
...options,
method: 'POST',
body: JSON.stringify(data)
});
}
put(endpoint, data, options = {}) {
return this.request(endpoint, {
...options,
method: 'PUT',
body: JSON.stringify(data)
});
}
delete(endpoint, options = {}) {
return this.request(endpoint, { ...options, method: 'DELETE' });
}
}
// Utilisation
const api = new APIClient('https://api.surveillance.local');
try {
// GET
const cas = await api.get('/cas/123');
// POST
const nouveauTemoin = await api.post('/temoins', {
nom: "Smith",
deposition: "..."
});
// PUT
await api.put(`/cas/${cas.id}`, {
...cas,
statut: 'résolu'
});
} catch (error) {
if (error.status === 401) {
// Rediriger vers login
window.location.href = '/login?expired=true';
} else if (error.status === 404) {
showNotification('Cas non trouvé', 'error');
} else {
console.error('Erreur API:', error);
showNotification(`Erreur: ${error.message}`, 'error');
}
}
Cas Pratique : Système de Surveillance
class SurveillanceSystem {
constructor(apiBase = 'https://api.surveillance.local') {
this.api = new APIClient(apiBase);
this.socket = null;
this.realtimeCallbacks = [];
}
// Cas
async getCas(id) {
return this.api.get(`/cas/${id}`);
}
async searchCas(criteres) {
return this.api.get('/cas/search', {
headers: {
'X-Search-Params': JSON.stringify(criteres)
}
});
}
async createCas(casData) {
return this.api.post('/cas', casData);
}
async updateCas(id, updates) {
return this.api.patch(`/cas/${id}`, updates);
}
async addEvidence(casId, evidence) {
const formData = new FormData();
formData.append('file', evidence.file);
formData.append('description', evidence.description);
formData.append('cas_id', casId);
return fetch(`${this.api.baseURL}/cas/${casId}/evidence`, {
method: 'POST',
body: formData
});
}
// Témoins
async getTemoin(id) {
return this.api.get(`/temoins/${id}`);
}
async getTemoinsParCas(casId) {
return this.api.get(`/cas/${casId}/temoins`);
}
async protegerTemoin(temoinId) {
return this.api.post(`/temoins/${temoinId}/protection`, {
niveau: 'maximum',
date_debut: new Date().toISOString()
});
}
// Réal-time updates
connectRealtime() {
this.socket = new WebSocket('wss://api.surveillance.local/ws');
this.socket.onopen = () => {
console.log('Connecté aux mises à jour en temps réel');
this.socket.send(JSON.stringify({
type: 'subscribe',
channels: ['cas_updates', 'alertes']
}));
};
this.socket.onmessage = (event) => {
const data = JSON.parse(event.data);
this.realtimeCallbacks.forEach(callback => callback(data));
};
this.socket.onclose = () => {
console.log('Déconnecté, reconnexion dans 5s...');
setTimeout(() => this.connectRealtime(), 5000);
};
}
onRealtimeUpdate(callback) {
this.realtimeCallbacks.push(callback);
}
// Export des données
async exportCas(id, format = 'json') {
const response = await this.api.get(`/cas/${id}/export?format=${format}`);
if (format === 'json') {
const blob = new Blob([JSON.stringify(response, null, 2)], {
type: 'application/json'
});
return blob;
} else if (format === 'pdf') {
return new Blob([response], { type: 'application/pdf' });
}
}
// Statistiques
async getStats(periode = '30j') {
return this.api.get(`/stats?periode=${periode}`);
}
}
// Utilisation
const surveillance = new SurveillanceSystem();
// S'abonner aux mises à jour
surveillance.onRealtimeUpdate((data) => {
if (data.type === 'cas_update') {
console.log(`Cas ${data.cas_id} mis à jour:`, data.changes);
updateUI(data.cas_id, data.changes);
}
if (data.type === 'alerte') {
showAlert(data.message, data.niveau);
}
});
// Recherche avancée
const resultats = await surveillance.searchCas({
statut: 'ouvert',
date_debut: '2024-01-01',
priorite: ['haute', 'critique'],
assigne_a: 'marlowe'
});
// Export d'un cas
const cas = await surveillance.getCas(123);
const pdfBlob = await surveillance.exportCas(123, 'pdf');
// Téléchargement
const url = URL.createObjectURL(pdfBlob);
const a = document.createElement('a');
a.href = url;
a.download = `cas-${cas.id}-${cas.titre}.pdf`;
a.click();
URL.revokeObjectURL(url);
Conclusion du Module
JSON et Fetch sont les artères du web moderne.
JSON transporte les données. Fetch les fait circuler.
Les règles du coursier :
- Toujours valider les entrées - JSON.parse peut échouer
- Toujours gérer les erreurs - Les réseaux sont capricieux
- Toujours vérifier les statuts HTTP - 200 ≠ succès dans tous les cas
- Toujours sérialiser avec JSON.stringify avant l'envoi
- Toujours utiliser async/await pour la lisibilité
Maintenant, tu peux faire parler les services entre eux.
Faire circuler l'information.
La ville est connectée.
**Prochain module : LE DOM **