A JavaScript object is a set of named values written as { key: value }. You read a value with obj.key or obj['key'], list keys with Object.keys and pairs with Object.entries. The text [object Object] appears when an object is turned into a string; use JSON.stringify(obj) to print its contents.
| Task | Code |
|---|---|
| Create | const user = { name: "Ana", age: 31 } |
| Read a property | user.name or user["name"] |
| Read with a variable key | user[key] |
| Safe nested read | user.address?.city |
| List keys / values / pairs | Object.keys(user) / Object.values(user) / Object.entries(user) |
| Shallow copy and merge | { ...user, age: 32 } |
| Deep copy | structuredClone(user) |
| Print the contents | JSON.stringify(user, null, 2) |
Updated
Objects are the shape almost every API response, config file and React prop arrives in. This guide covers the handful of operations you use daily, then deals with the two classic surprises: [object Object] showing up in the page or an alert, and a copy that secretly shares its nested data with the original.
{
"name": "Ana",
"age": 31,
"address": { "city": "Lisbon", "zip": "1100" },
"tags": ["admin", "editor"]
}Only the first line produces [object Object]. The others print readable contents.
const user = { name: "Ana", tags: ["admin"] };
el.textContent = "User: " + user; // User: [object Object]
el.textContent = "User: " + JSON.stringify(user); // User: {"name":"Ana","tags":["admin"]}
console.log("User:", user); // expandable object in DevTools
console.table(Object.entries(user)); // one row per keyA pre element keeps the line breaks and indentation that JSON.stringify adds with its third argument.
<pre>{
"name": "Ana",
"address": {
"city": "Lisbon"
}
}</pre>entries turns the object into pairs, map changes each value, fromEntries builds a new object.
const prices = { tea: 3, cake: 4.5, juice: 4 };
const withTax = Object.fromEntries(
Object.entries(prices).map(([item, p]) => [item, +(p * 1.2).toFixed(2)])
);
// { tea: 3.6, cake: 5.4, juice: 4.8 }The spread copy shares the inner address object; the clone does not.
const original = { name: "Ana", address: { city: "Lisbon" } };
const shallow = { ...original };
const deep = structuredClone(original);
shallow.address.city = "Porto";
console.log(original.address.city); // "Porto" (shared)
deep.address.city = "Faro";
console.log(original.address.city); // still "Porto"
Dot notation is shorter and works when the key is a valid identifier you know while writing the code. Brackets are required when the key lives in a variable, contains a space or a dash, or starts with a digit, for example headers['content-type'].
Reading a key that does not exist gives undefined rather than an error. Reading a property of that undefined is what throws, which is why optional chaining (user.address?.city) is so handy on API data.
Spread copies only the top level. The new object gets its own slots, but if a slot holds another object, both copies point at that same inner object, so editing copy.address.city also edits the original.
structuredClone walks the whole structure and builds new inner objects too. It does not keep class prototypes, getters or setters, so a class instance comes back as a plain object with the same data.
Something converted the object to a string: a + with text, a template literal, an alert, element.textContent or innerText. The default toString for plain objects returns the fixed text [object Object]. Print JSON.stringify(obj) instead, or pick the property you meant, such as obj.name.
Use for (const [key, value] of Object.entries(obj)) { ... }. A plain object is not iterable, so for...of on it directly throws a TypeError. for...in also works but includes inherited enumerable keys.
For a flat object, { ...obj } or Object.assign({}, obj) is enough. If the object holds nested objects or arrays, use structuredClone(obj), which copies every level. It handles Dates, Maps and Sets but throws a DataCloneError on functions and DOM nodes.
Object.entries(obj) gives an array of [key, value] pairs. Transform it with map or filter, then rebuild an object with Object.fromEntries(pairs). This is the usual way to map over an object's values.
A Map accepts any type as a key, remembers insertion order for all keys and has a size property. Plain objects use string or symbol keys and are what JSON produces. Note that JSON.stringify turns a Map into {}, so convert it first with Object.fromEntries(map).
Related: JavaScript loops compared, JavaScript array methods, JSON formatter, Save objects in localStorage, TypeScript interfaces.
Start for free or see all free tools. We.Inc is free to start, with no card needed. AI building is on paid plans from $20 a month.
We.Inc is an AI-powered website builder you can resell under your own brand. Launch a branded client dashboard, bill on Stripe Connect, and deliver AI-generated websites in minutes. White-label plans start at $99 a month for 25 client sites, with a 7-day free trial and no per-site fees.