Trois lignes de JavaScript, un alert(user) balancé vite fait, et l’écran affiche [object Object]. Pas le nom, pas l’email, pas la moindre propriété utile. Juste cette chaîne cryptique que 90 % des développeurs ont croisée au moins une fois en débuguant un prototype ou en concaténant un objet à une string. Ce retour, c’est la représentation par défaut produite par Object.prototype.toString() — et comprendre son mécanisme change la façon dont on manipule les objets en JavaScript pour de bon.
Que signifie « [object Object] » : la coercition objet vers string en 30 secondes
Quand JavaScript doit convertir un objet en chaîne de caractères (string), le moteur appelle la méthode toString() héritée via la chaîne de prototype. Pour un objet ordinaire, Object.prototype.toString() retourne la valeur [object Object] — le premier « object » indique le type interne, le second correspond au tag par défaut.
const user = { name: "Alice", age: 32 };
console.log("Utilisateur : " + user);
// output : "Utilisateur : [object Object]"
console.log(user);
// output : { name: "Alice", age: 32 }
La différence saute aux yeux. La concaténation avec + force une coercition en string, donc toString() s’exécute. Le console.log direct, lui, affiche la structure de l’objet sans conversion. Retenir cette distinction, c’est déjà résoudre la moitié des cas.
📌 À retenir :
[object Object]n’est pas une erreur. C’est la valeur retournée parObject.prototype.toString()quand aucuntoString()personnalisé n’existe sur l’objet ou son prototype.
Les 6 contextes où « [object Object] » apparaît — et pourquoi
Cas 1 : alert(objet)
const config = { theme: "dark", lang: "fr" };
alert(config);
// output dans le popup : [object Object]
alert() convertit tout argument en string avant l’affichage. Un objet sans toString() personnalisé retourne la valeur par défaut.
Cas 2 : concaténation "" + objet
const product = { name: "MacBook", price: 1499 };
const msg = "Produit : " + product;
console.log(msg);
// output : "Produit : [object Object]"
L’opérateur + avec une string déclenche ToPrimitive → toString() sur l’objet. Le résultat écrase vos propriétés.
Cas 3 : template string ${objet}
const settings = { mode: "auto" };
console.log(`Config : ${settings}`);
// output : "Config : [object Object]"
Les template literals appellent toString() sur chaque expression interpolée. Même mécanisme, même résultat.
Cas 4 : DOM display — innerHTML / textContent
const data = { title: "Mon article", views: 2340 };
document.getElementById("info").textContent = data;
// Le navigateur affiche : [object Object]
Assigner un objet à textContent ou innerHTML provoque la même coercition. Le DOM attend une string, pas un objet structuré.
Cas 5 : logger vs sérialiseur — la confusion console.log
const items = [{ id: 1 }, { id: 2 }];
console.log("Items : " + items);
// output : "Items : [object Object],[object Object]"
console.log("Items :", items);
// output : "Items :" [{ id: 1 }, { id: 2 }]
La virgule dans console.log passe l’objet comme argument séparé — pas de coercition. Le + déclenche toString() sur chaque élément du tableau.
Cas 6 : messages d’erreur et frameworks
function validate(value) {
if (typeof value !== "string") {
throw new Error("Attendu string, reçu : " + value);
}
}
validate({ key: "test" });
// Error: Attendu string, reçu : [object Object]
Les messages d’erreur dans les try/catch, les logs de frameworks comme React ou Vue, les assertions de test — tous concatènent des objets à des strings pour le display. Résultat identique à chaque fois.
⚠️ Attention : dans React, rendre un objet directement dans du JSX (
{myObj}) ne produit pas[object Object]mais une erreur runtime « Objects are not valid as a React child ». Le comportement diffère selon le contexte de display.
Pourquoi JavaScript retourne cette chaîne : toString(), Object.prototype et la chaîne de prototype
Comment un objet devient une string : la coercition interne
Quand le moteur JS a besoin d’une primitive string à partir d’un objet, il exécute l’algorithme ToPrimitive avec le hint "string". L’algorithme cherche d’abord toString(), puis valueOf(). Si toString() retourne une primitive, c’est terminé.
Pour un objet créé avec {} ou new Object(), aucun toString() propre n’existe. Le moteur remonte la chaîne de prototype jusqu’à Object.prototype.toString.
const obj = { a: 1 };
console.log(obj.toString === Object.prototype.toString);
// output : true
D’où vient le format [object Type]
La spécification ECMAScript définit Object.prototype.toString() pour retourner une string au format "[object " + tag + "]". Le tag par défaut est "Object" pour les objets ordinaires.
console.log(Object.prototype.toString.call(42));
// output : "[object Number]"
console.log(Object.prototype.toString.call("abc"));
// output : "[object String]"
console.log(Object.prototype.toString.call(null));
// output : "[object Null]"
console.log(Object.prototype.toString.call(undefined));
// output : "[object Undefined]"
Object.prototype.toString.call(objet) — avec this lié à l’argument via call — sert de type-checker fiable depuis ES5. C’est d’ailleurs la technique que des bibliothèques comme Lodash utilisent en interne.
Symbol.toStringTag : personnaliser le tag du type
Depuis ES6, la propriété Symbol.toStringTag permet de modifier le tag retourné par Object.prototype.toString().
const api = {
[Symbol.toStringTag]: "APIClient",
endpoint: "/users"
};
console.log(Object.prototype.toString.call(api));
// output : "[object APIClient]"
console.log("" + api);
// output : "[object Object]" — toString() direct, pas le call
Attention au piège : Symbol.toStringTag modifie le résultat de Object.prototype.toString.call(), mais la concaténation appelle toString() sur l’objet lui-même, qui retourne toujours [object Object] sans override explicite du prototype.
Tableau diagnostic : du symptôme au correctif
| Cause | Code problématique | Pourquoi | Fix recommandé | Output attendu |
|---|---|---|---|---|
alert(obj) | alert(user) | Coercition → toString() | alert(JSON.stringify(user)) | {"name":"Alice"} |
"" + obj | "Info: " + user | Concat → toString() | "Info: " + user.name | "Info: Alice" |
| Template string | `User: ${obj}` | Interpolation → toString() | `User: ${obj.name}` | "User: Alice" |
| DOM textContent | el.textContent = obj | Coercition string | el.textContent = obj.name | "Alice" |
console.log("" + obj) | Log avec concat | Concat avant log | console.log("user:", obj) | user: {name: "Alice"} |
| Error message | throw new Error("Got: " + obj) | Concat dans message | JSON.stringify(obj) dans le message | "Got: {\"name\":\"Alice\"}" |
| Array.join | [obj1, obj2].join(", ") | join appelle toString() par élément | .map(o => o.name).join(", ") | "Alice, Bob" |
| JSON key | obj[autreObj] = value | Clé convertie en string | Utiliser Map pour des clés objet | Map { {id:1} => value } |
Règle de base : pour du debug, passer l’objet comme argument séparé à console.log. Pour du display utilisateur, accéder aux propriétés directement ou sérialiser via JSON.stringify.
Solution 1 — Inspecter correctement dans la console
console.log vs console.dir : quand utiliser chaque méthode
console.log affiche une représentation interactive de l’objet dans la plupart des DevTools. console.dir force un affichage en arbre des propriétés, y compris celles du prototype.
const el = document.querySelector("h1");
console.log(el);
// output : <h1>Mon titre</h1> (rendu HTML dans Chrome)
console.dir(el);
// output : h1 { accessKey: "", align: "", ... } (propriétés JS)
Pour les objets classiques, console.log suffit. console.dir devient utile quand on inspecte des éléments DOM ou des objets avec un prototype complexe.
Le piège de la référence : un objet muté après le log
const state = { count: 0 };
console.log(state);
state.count = 42;
En ouvrant la console après l’exécution, le console.log peut afficher { count: 42 } — pas 0. Le log stocke une référence, pas un snapshot. Pour capturer la value à l’instant du log :
console.log(JSON.parse(JSON.stringify(state)));
// output garanti : { count: 0 }
💡 Conseil : dans Node.js 17+,
structuredClone(state)remplace le hack stringify/parse pour cloner un objet avant le log — plus propre et supporte les types queJSON.stringifyignore (Date, RegExp, Map).
Solution 2 — JSON.stringify : obtenir une string lisible
Sérialiser un objet : basique et pretty print
const config = { theme: "dark", lang: "fr", debug: false };
console.log(JSON.stringify(config));
// output : '{"theme":"dark","lang":"fr","debug":false}'
console.log(JSON.stringify(config, null, 2));
// output :
// {
// "theme": "dark",
// "lang": "fr",
// "debug": false
// }
Le troisième argument contrôle l’indentation. 2 produit un JSON lisible, pratique pour le display en UI ou dans des logs structurés.
Limites : circular, function, undefined, BigInt
JSON.stringify ne gère pas tout. Voici ce qui casse :
const a = {};
a.self = a; // circular
JSON.stringify(a);
// TypeError: Converting circular structure to JSON
const b = { fn: function() {}, val: undefined };
console.log(JSON.stringify(b));
// output : '{}' — function et undefined sont ignorés
const c = { big: 42n };
JSON.stringify(c);
// TypeError: Do not know how to serialize a BigInt
Le null passe sans problème — JSON.stringify({ x: null }) retourne '{"x":null}'. Les propriétés à value undefined disparaissent silencieusement. Les fonctions aussi.
Le replacer : contrôler les properties sérialisées
const user = { name: "Alice", password: "secret", role: "admin" };
const safe = JSON.stringify(user, ["name", "role"]);
console.log(safe);
// output : '{"name":"Alice","role":"admin"}'
Le deuxième argument — un tableau de clés ou une function — filtre les propriétés. On évite les fuites de données sensibles en production.
const withDates = JSON.stringify(event, (key, value) => {
if (value instanceof Date) return value.toISOString();
return value;
});
Si vous travaillez sur un projet tech plus large — choisir le bon PC portable en 2026 par exemple pour du dev intensif — ces techniques de debug deviennent vite quotidiennes.
Solution 3 — Personnaliser l’affichage : toString(), toJSON() et bonnes pratiques
Custom toString : rendre l’objet lisible
const product = {
name: "Galaxy S26",
price: 899,
toString() {
return `${this.name} (${this.price} €)`;
}
};
console.log("Produit : " + product);
// output : "Produit : Galaxy S26 (899 €)"
alert(product);
// output dans le popup : "Galaxy S26 (899 €)"
La méthode toString() doit retourner une string — rien d’autre. Si elle retourne un objet, JavaScript passe à valueOf() puis lève une TypeError.
toJSON : contrôler la sérialisation via stringify
const session = {
id: "abc-123",
user: { name: "Alice" },
_internal: { token: "xyz" },
toJSON() {
return { id: this.id, user: this.user.name };
}
};
console.log(JSON.stringify(session));
// output : '{"id":"abc-123","user":"Alice"}'
toJSON() est appelée par JSON.stringify avant la sérialisation. Pratique pour les objets métier (domain objects) où certaines properties sont privées. Pour du debug rapide, mieux vaut rester sur console.log direct.
Objets à prototype null (nullprotoobj) : quand toString et hasOwnProperty disparaissent
Créer un nullprotoobj avec Object.create(null)
const dict = Object.create(null);
dict.name = "test";
dict.key = "value";
console.log(dict);
// output : [Object: null prototype] { name: "test", key: "value" }
Un nullprotoobj n’a pas de prototype. Zéro. Pas de toString, pas de hasOwnProperty, pas de valueOf. C’est un dictionnaire pur — exactement ce que des bibliothèques comme Express utilisent en interne pour les headers HTTP.
Pourquoi hasOwnProperty et toString n’existent pas
const nullprotoobj = Object.create(null);
nullprotoobj.key = "data";
nullprotoobj.hasOwnProperty("key");
// TypeError: nullprotoobj.hasOwnProperty is not a function
nullprotoobj.toString();
// TypeError: nullprotoobj.toString is not a function
"" + nullprotoobj;
// TypeError: Cannot convert object to primitive value
Chaque appel échoue. La chaîne de prototype est vide — Object.prototype n’est pas dans la chaîne. Les méthodes héritées habituelles n’existent tout simplement pas.
Pattern sûr : Object.prototype.hasOwnProperty.call(...)
const nullprotoobj = Object.create(null);
nullprotoobj.name = "test";
// Mauvais — crash
// nullprotoobj.hasOwnProperty("name");
// Bon — fonctionne sur n'importe quel objet
console.log(Object.prototype.hasOwnProperty.call(nullprotoobj, "name"));
// output : true
// Encore mieux — ES2022+
console.log(Object.hasOwn(nullprotoobj, "name"));
// output : true
Object.hasOwn() est le remplacement moderne de hasOwnProperty — plus court, plus sûr, fonctionne sur les nullprotoobj sans contorsion.
Impact sur stringify, console et display
const nullprotoobj = Object.create(null);
nullprotoobj.a = 1;
nullprotoobj.b = null;
console.log(JSON.stringify(nullprotoobj));
// output : '{"a":1,"b":null}' — stringify fonctionne
console.log(nullprotoobj);
// output : [Object: null prototype] { a: 1, b: null }
console.log("" + nullprotoobj);
// TypeError — pas de toString, pas de valueOf
Bonne nouvelle : JSON.stringify fonctionne sur un nullprotoobj parce que la sérialisation n’appelle pas toString(). Le display direct via concaténation, par contre, plante.
📌 À retenir : si votre code manipule des objets qui pourraient être des nullprotoobj (dictionnaires, caches, configs parsées), toujours utiliser
Object.hasOwn()etJSON.stringify()— jamais les méthodes d’instance.
Le lien entre [object Object] et la POO JavaScript : function, new, this et prototype
Un constructeur, une instance, un prototype
function Smartphone(name, price) {
this.name = name;
this.price = price;
}
Smartphone.prototype.display = function() {
return `${this.name} — ${this.price} €`;
};
const phone = new Smartphone("Pixel 9", 799);
console.log(phone.display());
// output : "Pixel 9 — 799 €"
console.log("Mon tel : " + phone);
// output : "Mon tel : [object Object]"
L’instance créée via new hérite de Smartphone.prototype, qui hérite lui-même de Object.prototype. La méthode display() existe, mais toString() non — donc la concaténation retourne [object Object].
Corriger ça en 2 lignes
Smartphone.prototype.toString = function() {
return this.display();
};
console.log("Mon tel : " + phone);
// output : "Mon tel : Pixel 9 — 799 €"
Ajouter toString() sur le prototype suffit. Toutes les instances en héritent automatiquement — this pointe vers l’instance qui appelle la méthode, pas vers le prototype.
Quand on teste ce genre de code sur un setup avec la bonne tablette Android, la console des DevTools mobiles rend le debug un peu plus laborieux qu’un écran 27 pouces. Bon à savoir.
Objets spéciaux : Date, Error, Array, Map/Set — représentation et sérialisation
| Type d’objet | toString() retourne | JSON.stringify() retourne | Recommandation |
|---|---|---|---|
Object | [object Object] | '{"key":"value"}' | Stringify ou accès propriétés |
Array | "1,2,3" | '[1,2,3]' | Stringify pour structure, join() pour display |
Date | "Mon Feb 24 2026..." | '"2026-02-24T..."' | .toLocaleDateString() pour l’utilisateur |
Error | "Error: message" | '{}' — vide | .message + .stack |
RegExp | "/pattern/flags" | '{}' — vide | .source + .flags |
Map | [object Map] | '{}' — vide | Object.fromEntries(map) puis stringify |
Set | [object Set] | '{}' — vide | [...set] puis stringify |
null | N/A (pas un objet) | 'null' | Vérifier avant accès |
Chaque type d’objet a son comportement propre (particular_object). Error et Map sont les plus traîtres — JSON.stringify retourne un objet vide '{}' parce que leurs properties ne sont pas énumérables. Toujours passer par une méthode dédiée pour ces cas.
const err = new Error("Crash");
console.log(JSON.stringify(err));
// output : '{}'
console.log(JSON.stringify({ message: err.message, stack: err.stack }));
// output : '{"message":"Crash","stack":"Error: Crash\\n at ..."}'
Utilitaires prêts à copier : safeStringify et affichage key/value
safeStringify : éviter le crash sur les références circulaires
function safeStringify(obj, indent = 2) {
const seen = new WeakSet();
return JSON.stringify(obj, (key, value) => {
if (typeof value === "object" && value !== null) {
if (seen.has(value)) return "[Circular]";
seen.add(value);
}
return value;
}, indent);
}
const a = { name: "test" };
a.self = a;
console.log(safeStringify(a));
// output :
// {
// "name": "test",
// "self": "[Circular]"
// }
Le WeakSet stocke les objets déjà visités. Quand un objet apparaît deux fois (circular), le replacer retourne la string "[Circular]" au lieu de crasher. Fonctionne aussi si value est null — le guard value !== null filtre ce cas.
Afficher toutes les propriétés : key/value proprement
function prettyPrint(obj) {
if (obj === null || obj === undefined) {
return String(obj);
}
const keys = Object.keys(obj);
if (keys.length === 0) return "{}";
return keys.map(key => `${key}: ${obj[key]}`).join("\n");
}
const nullprotoobj = Object.create(null);
nullprotoobj.host = "localhost";
nullprotoobj.port = 3000;
console.log(prettyPrint(nullprotoobj));
// output :
// host: localhost
// port: 3000
console.log(prettyPrint(null));
// output : "null"
Object.keys() fonctionne sur les nullprotoobj — pas besoin de hasOwnProperty. Ce code gère null, undefined et les objets vides sans erreur. À utiliser pour le debug, pas en production (préférer un logger structuré type Winston ou Pino).
💡 Conseil : en Node.js,
require('util').inspect(obj, { depth: null, colors: true })fait le même travail avec la gestion native des circular, des prototypes et de la coloration syntaxique. En environnement navigateur, les DevTools font déjà ça viaconsole.dir.
Checklist : ne plus jamais afficher « [object Object] » par accident
Do
- Debug : passer l’objet en argument séparé —
console.log("user:", obj)pasconsole.log("user: " + obj) - Display utilisateur : accéder aux propriétés —
obj.name,obj.title - Sérialisation :
JSON.stringify(obj)ouJSON.stringify(obj, null, 2)pour le pretty print - Objets métier : implémenter
toString()qui retourne une string descriptive - Clés objet : utiliser
Mapau lieu de{}quand les clés ne sont pas des strings - Nullprotoobj :
Object.hasOwn()etJSON.stringify(), jamais de méthodes d’instance
Don’t
- Concaténer un objet à une string avec
+ - Interpoler un objet dans un template literal sans accéder à une propriété
- Assigner un objet à
textContentouinnerHTMLdirectement - Supposer que
console.log("" + x)affiche la même chose queconsole.log(x) - Appeler
toString()ouhasOwnProperty()sur un objet dont le prototype est inconnu - Ignorer les retours silencieux de
JSON.stringify(undefinedpour les functions,'{}'pour Error/Map/Set)
Mauvais code :
const user = { name: "Bob", role: "dev" };
document.getElementById("greeting").textContent = "Bienvenue " + user;
// display : "Bienvenue [object Object]"
Bon code :
document.getElementById("greeting").textContent = `Bienvenue ${user.name} (${user.role})`;
// display : "Bienvenue Bob (dev)"
Quand on travaille sur un projet qui tourne sur un bon smartphone photo — app hybride, PWA, debug à distance — garder ces réflexes évite de perdre 20 minutes sur un affichage cassé.
FAQ — « [object Object] » : réponses directes
Qu’est-ce que « [object Object] » en JavaScript ?
C’est la string retournée par Object.prototype.toString() quand JavaScript convertit un objet ordinaire en chaîne de caractères. Le premier mot indique la catégorie (object), le second le tag interne (Object). Ce n’est pas une erreur — c’est le comportement par défaut pour tout objet sans méthode toString() personnalisée sur son prototype.
Comment corriger [object Object] dans un alert() ?
Remplacer alert(monObjet) par alert(JSON.stringify(monObjet, null, 2)). Le stringify sérialise toutes les propriétés en JSON lisible. Pour un affichage ciblé, accéder directement aux propriétés : alert(monObjet.name). Éviter alert() en production — console.log avec l’objet en argument séparé donne un résultat interactif dans les DevTools.
Pourquoi JSON.stringify retourne '{}' sur certains objets ?
Les objets Error, Map, Set et RegExp n’ont pas de propriétés énumérables propres — leurs données sont stockées en interne. JSON.stringify(new Error("test")) retourne '{}' parce que message et stack ne sont pas énumérables par défaut. Solution : extraire manuellement — JSON.stringify({ message: err.message, stack: err.stack }).