Object Toolbox 0/8

JavaScript basicsObjects in plain wordsOne tool at a time

Object
Toolbox

An object keeps values under names, like labelled drawers. This guide shows how to read, list, change, copy, reshape and lock those drawers, with a small example for each that you can run.

JavaScript ES202423 tools
01

Make and read

Build an object with curly braces and read any value back with a dot or square brackets.

Start heredot[ ]?.Must know

An array keeps values in a numbered row. An object keeps values under names, like labelled drawers in a cabinet. Each name is a key, and what sits in the drawer is its value. Together they make a property.

Picture it: one object

username"Asha"age29home city"Pune"user.age29Ask for a key, get its value.
obj.key

Reads a value when you know the key and it has no spaces.

See the example
Gives the value or undefinedOnly reads
obj["key"]

Reads a value when the key has spaces or is stored in a variable.

See the example
Gives the value or undefinedOnly reads
obj?.key

Reads safely. If obj is missing, you get undefined instead of a crash.

See the example
Gives the value or undefinedOnly reads
a ?? b

Uses b only when a is null or undefined. Great for fallbacks.

See the example
Gives a or bOnly reads
"key" in obj

Checks if the key exists, even if its value is undefined.

See the example
Gives true or falseOnly reads
Object.hasOwn(obj, key)

Checks if the object itself owns that key.

See the example
Gives true or falseOnly reads
read.jsJAVASCRIPT
const user = {
  name: "Asha",
  age: 29,
  "home city": "Pune",           // a key with a space needs quotes
  greet() { return `Hi, I am ${this.name}`; },
};

console.log(user.name);          // dot: when you know the key
console.log(user["home city"]);  // brackets: spaces or a key in a variable
const field = "age";
console.log(user[field]);
console.log(user.greet());       // a function inside an object is a method
console.log(user.email);         // a missing key gives undefined, not an error

Easy way to remember: use the dot by default. Switch to square brackets when the key has a space or lives in a variable.

Terminal OUTPUT
$ node read.js
Asha
Pune
29
Hi, I am Asha
undefined
safe-read.jsJAVASCRIPT
const order = { id: 7, customer: { name: "Ravi" } };

// ?. stops early instead of crashing when something is missing
console.log(order.customer?.name);
console.log(order.shipping?.city);          // undefined, no crash

// ?? gives a fallback only for null or undefined
console.log(order.shipping?.city ?? "not set");

// checking if a key exists
console.log("id" in order, Object.hasOwn(order, "total"));

Why it matters: ?. and ?? together replace long chains of if checks when data might be missing.

Terminal OUTPUT
$ node safe-read.js
Ravi
undefined
not set
true false
shorthand.jsJAVASCRIPT
const name = "Meera";
const role = "admin";
const key = "last login";

const account = {
  name,                  // same as name: name
  role,
  [key]: "today",        // computed key: the value of key becomes the name
  [`is_${role}`]: true,
};
console.log(account);

Handy shortcuts: when a variable has the same name as the key, write it once. Square brackets around a key let you build its name from a value.

Terminal OUTPUT
$ node shorthand.js
{ name: 'Meera', role: 'admin', 'last login': 'today', is_admin: true }
02

List keys and values

Turn an object into arrays of keys, values or pairs so you can loop, count and use array methods.

Loopingkeysvaluesentries

Picture it: Object.entries

pricestea20coffee45Object.entries(prices)["tea", 20]["coffee", 45]Each key and value becomes a small pair.
Object.keys(obj)

Lists every key.

See the example
Gives an array of keysOnly reads
Object.values(obj)

Lists every value.

See the example
Gives an array of valuesOnly reads
Object.entries(obj)

Lists every key with its value, as pairs.

See the example
Gives an array of pairsOnly reads
for...of + entries

Loops over pairs and unpacks each one into two names.

See the example
Gives nothingOnly reads
list.jsJAVASCRIPT
const prices = { tea: 20, coffee: 45, juice: 60 };

console.log(Object.keys(prices));     // the names
console.log(Object.values(prices));   // the values
console.log(Object.entries(prices));  // [name, value] pairs

for (const [item, price] of Object.entries(prices)) {
  console.log(`${item} costs ${price}`);
}
console.log(Object.keys(prices).length, "items");

Easy way to remember: objects have no length and no map. Turn them into an array first, then use every array tool you already know.

Terminal OUTPUT
$ node list.js
[ 'tea', 'coffee', 'juice' ]
[ 20, 45, 60 ]
[ [ 'tea', 20 ], [ 'coffee', 45 ], [ 'juice', 60 ] ]
tea costs 20
coffee costs 45
juice costs 60
3 items
03

Change and remove

Add, update and delete keys, or merge two objects into one.

Changingdeletespread ...Object.assign

Picture it: spread merge

basetheme"light"size14+minesize16{ ...base, ...mine }new objecttheme"light"size16Later keys win when names clash.
obj.key = value

Adds the key if it is new, or replaces the value if it exists.

See the example
Gives the valueChanges it
delete obj.key

Removes the key and its value.

See the example
Gives trueChanges it
{ ...a, ...b }

Builds a new object from a, then b. Later keys win.

See the example
Gives a new objectMakes a copy
Object.assign(target, ...sources)

Copies keys from the sources into target.

See the example
Gives targetChanges it
change.jsJAVASCRIPT
const car = { brand: "Tata", year: 2021 };

car.color = "blue";        // add a new key
car.year = 2022;           // update an existing key
delete car.brand;          // remove a key
console.log(car);

// spread builds a new object; later keys win
const base = { theme: "light", size: 14 };
const mine = { size: 16 };
console.log({ ...base, ...mine, lang: "hi" });

// Object.assign copies keys into the first object and changes it
const target = { a: 1 };
Object.assign(target, { b: 2 }, { c: 3 });
console.log(target);

Rule of thumb: prefer spread when you want a fresh object and the old one untouched. Use Object.assign only when you really mean to change the first object.

Terminal OUTPUT
$ node change.js
{ year: 2022, color: 'blue' }
{ theme: 'light', size: 16, lang: 'hi' }
{ a: 1, b: 2, c: 3 }
04

Copy safely

Know the difference between a second name, a shallow copy and a deep copy.

CopiesreferencestructuredCloneMust know

A variable does not hold the object itself. It holds directions to where the object lives. Writing const b = a copies the directions, so both names open the same box.

Picture it: two names, one box

originalsameBoxone objectname"Kiran R"tags[ ... ]Change it through either name and both see it.
HowTop level copiedInner objects copied
const b = aNoNo
{ ...a }YesNo
Object.assign({}, a)YesNo
structuredClone(a)YesYes
copy.jsJAVASCRIPT
const original = { name: "Kiran", tags: ["new"] };

const sameBox = original;              // not a copy, just a second name
sameBox.name = "Kiran R";
console.log(original.name);

const shallow = { ...original };       // copies the top level only
shallow.name = "K";
shallow.tags.push("vip");              // the inner array is still shared
console.log(original.name, original.tags);

const deep = structuredClone(original); // copies every level
deep.tags.push("gold");
console.log(original.tags, deep.tags);

Why it matters: a shallow copy looks safe until you change a nested array. Use structuredClone when the object has objects inside it.

Terminal OUTPUT
$ node copy.js
Kiran R
Kiran R [ 'new', 'vip' ]
[ 'new', 'vip' ] [ 'new', 'vip', 'gold' ]
05

Build and transform

Turn pairs back into an object, change every value at once, and group a list into buckets.

TransformingfromEntriesgroupBy

Picture it: Object.fromEntries

["red", "#FF4D6D"]["amber", "#FFB703"]Object.fromEntries(pairs)new objectred"#FF4D6D"amber"#FFB703"The opposite of Object.entries.
Object.fromEntries(pairs)

Builds an object from a list of [key, value] pairs.

See the example
Gives a new objectMakes a copy
Object.groupBy(list, fn)

Sorts list items into buckets named by what your helper returns.

See the example
Gives an object of arraysMakes a copy
build.jsJAVASCRIPT
// entries in, object out
const pairs = [["red", "#FF4D6D"], ["amber", "#FFB703"]];
console.log(Object.fromEntries(pairs));

// the change pattern: entries, then map, then fromEntries
const stock = { pens: 12, books: 3, bags: 0 };
const doubled = Object.fromEntries(
  Object.entries(stock).map(([item, qty]) => [item, qty * 2])
);
console.log(doubled);

// keep only some keys
const inStock = Object.fromEntries(
  Object.entries(stock).filter(([, qty]) => qty > 0)
);
console.log(inStock);

The recipe: Object.entries, then map or filter, then Object.fromEntries. It is the object version of map and filter.

Terminal OUTPUT
$ node build.js
{ red: '#FF4D6D', amber: '#FFB703' }
{ pens: 24, books: 6, bags: 0 }
{ pens: 12, books: 3 }
group.jsJAVASCRIPT
const people = [
  { name: "Asha", team: "backend" },
  { name: "Ravi", team: "frontend" },
  { name: "Meera", team: "backend" },
];

// Object.groupBy sorts a list into buckets by the key you return
const byTeam = Object.groupBy(people, (p) => p.team);
console.log(Object.keys(byTeam));
console.log(byTeam.backend.map((p) => p.name));

Needs: Node 21 or newer, or any current browser. Before that, people wrote this with reduce.

Terminal OUTPUT
$ node group.js
[ 'backend', 'frontend' ]
[ 'Asha', 'Meera' ]
06

Lock it down

Stop an object from being changed with seal and freeze, and learn why const is not enough.

Safetyfreezesealconst

const only stops you from pointing the name at a different object. The object itself can still change. To protect the contents, lock the object.

Can youNormalObject.sealObject.freeze
Add a keyYesNoNo
Change a valueYesYesNo
Delete a keyYesNoNo
Object.freeze(obj)

Locks the object completely. Nothing can be added, changed or removed.

See the example
Gives the same objectChanges it
Object.seal(obj)

Keeps the keys fixed but lets values change.

See the example
Gives the same objectChanges it
Object.isFrozen(obj)

Checks if the object is frozen.

See the example
Gives true or falseOnly reads
lock.jsJAVASCRIPT
"use strict";
const config = Object.freeze({ port: 3000, db: { host: "localhost" } });

try {
  config.port = 8080;               // not allowed on a frozen object
} catch (err) {
  console.log(err.constructor.name + ":", err.message);
}
config.db.host = "db.internal";     // freeze is shallow: inner objects can change
console.log(config.port, config.db.host);

const seal = Object.seal({ level: 1 });
seal.level = 2;                     // sealed: changing is fine
try { seal.extra = true; } catch (err) { console.log("seal blocks new keys"); }
console.log(seal, Object.isFrozen(config), Object.isSealed(seal));

Watch for this: freeze is shallow. Inner objects stay changeable unless you freeze them too. Outside strict mode, blocked changes fail silently instead of throwing.

Terminal OUTPUT
$ node lock.js
TypeError: Cannot assign to read only property 'port' of object '#<Object>'
3000 db.internal
seal blocks new keys
{ level: 2 } true true
07

Unpack and compare

Pull values out with destructuring, and check if two objects really match.

Destructuringrenamedefaults===
const { a } = obj

Makes a variable a from obj.a.

See the example
Gives new variablesOnly reads
{ a: b }

Reads key a but names the variable b.

See the example
Gives a renamed variableOnly reads
{ a = 5 }

Uses 5 when obj.a is undefined.

See the example
Gives a value or the defaultOnly reads
{ a, ...rest }

Collects every other key into a new object.

See the example
Gives a new objectMakes a copy
unpack.jsJAVASCRIPT
const book = { title: "Dune", pages: 412, author: { name: "Frank" } };

// pull keys out into variables
const { title, pages } = book;
console.log(title, pages);

// rename, give a default, and reach inside
const { title: heading, rating = 4, author: { name } } = book;
console.log(heading, rating, name);

// rest collects the keys you did not name
const { pages: _, ...withoutPages } = book;
console.log(withoutPages);

Easy way to remember: destructuring is the reverse of the shorthand from module 01. The braces go on the left of the equals sign.

Terminal OUTPUT
$ node unpack.js
Dune 412
Dune 4 Frank
{ title: 'Dune', author: { name: 'Frank' } }

Two objects are equal with === only when they are the very same box. Two boxes with the same contents are still two boxes.

compare.jsJAVASCRIPT
const a = { x: 1 };
const b = { x: 1 };
const c = a;

console.log(a === b);   // false: two different boxes with the same inside
console.log(a === c);   // true: the same box

// a simple equal check for flat objects
const sameShape = (p, q) =>
  Object.keys(p).length === Object.keys(q).length &&
  Object.keys(p).every((k) => p[k] === q[k]);
console.log(sameShape(a, b), sameShape(a, { x: 2 }));

Rule of thumb: compare the keys and values yourself for flat objects. For deep objects, use a tested helper from your test library.

Terminal OUTPUT
$ node compare.js
false
true
true false
08

All together

Merge defaults with saved settings, clean them, find what changed and unpack what a page needs.

Putting it togetherspreadfromEntriesfreezedestructuring

Real apps do this all the time. The app has default settings, a person saves a few of their own, and the page needs one clean, final set. Each step below uses a tool from an earlier module.

settings.jsJAVASCRIPT
// defaults the app ships with, frozen so nobody changes them by mistake
const DEFAULTS = Object.freeze({ theme: "light", fontSize: 14, alerts: true });

// what one person saved, which may be partial
const saved = { fontSize: 18, alerts: false, oldKey: "x" };

// 1. merge: saved values win over defaults
const merged = { ...DEFAULTS, ...saved };

// 2. keep only keys the app knows about
const clean = Object.fromEntries(
  Object.entries(merged).filter(([key]) => Object.hasOwn(DEFAULTS, key))
);

// 3. list what this person changed
const changed = Object.keys(clean).filter((k) => clean[k] !== DEFAULTS[k]);

// 4. unpack what the page needs
const { theme, fontSize = 14 } = clean;

console.log(clean);
console.log("changed:", changed.join(", "));
console.log(`render ${theme} theme at ${fontSize}px`);

What each step uses: freeze from module 06, spread from 03, entries and fromEntries from 02 and 05, hasOwn from 01, and destructuring from 07.

Terminal OUTPUT
$ node settings.js
{ theme: 'light', fontSize: 18, alerts: false }
changed: fontSize, alerts
render light theme at 18px
TerminalBASH
# save any example as a file and run it with Node 22
$ node settings.js
# or paste it into your browser console with F12

Which one do I need?

Start from what you want to do, then read across to the tool and what it gives back.

I want toUseYou get
Read a valueobj.key or obj["key"]the value or undefined
Read without crashingobj?.key ?? fallbackthe value or your fallback
Check a key existsObject.hasOwn(obj, key)true or false
List the keysObject.keysan array of keys
List the valuesObject.valuesan array of values
Loop over everythingObject.entriesan array of pairs
Merge two objects{ ...a, ...b }a new object
Remove a keydelete obj.keythe same object, changed
Copy with nested partsstructuredClonea deep copy
Change every valueentries → map → fromEntriesa new object
Group a listObject.groupByan object of arrays
Stop all changesObject.freezea locked object
Pull out a few valuesconst { a, b } = objnew variables