Module 8 · Objects, Destructuring, Spread and JSON
Objects, the Picture: Named Boxes, References and Two Names for One Box
In this lesson
- Build an object with a literal, and read, add and change its keys with dot and bracket access.
- Picture a variable as a name that points at an object, so two names can share one box.
- Explain why
constlets the object inside change, and why a spread copy is a new box.
Bob runs the phone list of a cycling club. In Module 7 he kept each member as an array: ["Rafi", "01711", "Dhaka"]. It worked for a week. Then he added an email at index 2, and every line that read the city as member[2] started printing emails. Nobody remembers which number means what.
Bob does not want slots by number. He wants slots by name: the name, the phone, the city. That is exactly what an object is. In this module you learn objects properly. You also learn the one idea that explains most object bugs: a variable does not hold an object, it points at one.
An object is a set of named boxes
An object is one value that holds other values under names. Each name is a key, and a key with its value is a property. You write an object with curly braces. Inside, each property is the key, a colon and the value, with commas between properties. This is an object literal.
const member = { name: "Rafi", phone: "01711", city: "Dhaka" };
console.log(member);
console.log(member.city);
console.log(Object.keys(member).length);
{ name: 'Rafi', phone: '01711', city: 'Dhaka' }
Dhaka
3
The first line prints the whole object. The second reads one value by its key. The third counts the keys with Object.keys, which returns the keys as an array. Lesson 02 covers it fully.
A value can be anything: a number, a string, a boolean, an array, even another object. The keys are strings. You may write them without quotes when they are plain words, as above.
Reading a key: dot and brackets
There are two ways to read a property. The dot form, member.city, is short and clear. The bracket form, member["city"], takes a string, so the key can come from a variable or from input.
const member = { name: "Rafi", phone: "01711", city: "Dhaka" };
const field = "phone";
console.log(member.name);
console.log(member["city"]);
console.log(member[field]);
console.log(member.email);
Rafi
Dhaka
01711
undefined
The last line asks for a key that does not exist. JavaScript does not stop. It gives undefined, just like an array read past its end. Use the dot when you know the key while writing the code. Use brackets when the key is only known while the program runs.
Adding and changing a key
An object is not fixed. Assigning to a key that does not exist yet adds it. Assigning to a key that exists replaces its value.
const member = { name: "Rafi" };
member.city = "Dhaka";
member["phone"] = "01711";
member.city = "Sylhet";
console.log(member);
console.log(Object.keys(member));
{ name: 'Rafi', city: 'Sylhet', phone: '01711' }
[ 'name', 'city', 'phone' ]
Look at the order of the keys. city was added first and changed later, yet it keeps its second place. A changed key stays where it was. A new key goes to the end. Lesson 05 gives the full order rule, which has one surprise for keys that look like numbers.
A name points at an object
Here is the idea that the rest of this module stands on. A variable that "holds" an object really holds a reference: an arrow to where the object lives. The object itself is the box. The variable is a name with an arrow to that box.
Numbers and strings are different. A variable holding 5 holds the value itself. When you copy it, you copy the 5. When you copy a variable that refers to an object, you copy the arrow, not the box.
const rafi = { name: "Rafi", city: "Dhaka" };
const friend = rafi;
friend.city = "Sylhet";
console.log(rafi.city);
console.log(friend === rafi);
Sylhet
true
There is only one object here. friend = rafi gave the same arrow a second name. The write through friend lands in the one box, so reading through rafi shows it. === on two objects asks "is this the same box?", and here it is.
const keeps the arrow, not the contents
In Module 1, Maria ran this and was surprised that it worked:
const list = [1, 2];
list.push(3);
console.log(list);
[ 1, 2, 3 ]
Now you can answer her. An array is an object, so list holds a reference. const promises that the name list keeps the same arrow forever. It makes no promise about the box. push changes the box, so const has nothing to refuse. What const refuses is list = [9], which would point the name at a new box.
The same holds for every object. const member = {} can gain keys all day. If you want the box itself frozen, Object.freeze does that; Lesson 02 shows it.
A copy is a new box
To get a second, separate object, you must build a new box. The quickest way is spread: { ...rafi } makes a new object and copies every property of rafi into it. Lesson 02 shows the trap inside: the copy is only one level deep.
Watch the five lines in the widget below, one step per line. The names are on the left, the boxes on the right, and an arrow is a reference.
Step 2 is the heart of it: no new box appears, only a second arrow. Step 4 is the opposite: a whole new box, filled with the same values. After step 5 the two boxes hold different cities.
Objects at a glance
const o = { key: value, other: value2 }; // an object literal
o.key o["key"] o[name] // read by dot, by string, by a variable
o.newKey = v; // add a key, or replace its value
const p = o; // a second name for the same box
const c = { ...o }; // a new box with the same properties
p === o // true only for the same box
Bob's input is a member's name, phone and city, as three words. This program builds one object from them and prints a card. The keys come from the code; the values come from the input.
const input = require("fs").readFileSync(0, "utf8");
const tokens = input.split(/\s+/).filter(Boolean);
const member = { name: tokens[0], phone: tokens[1], city: tokens[2] };
member.joined = 2026;
for (const key of Object.keys(member)) {
console.log(key + ": " + member[key]);
}
console.log("fields " + Object.keys(member).length);
name: Nadia
phone: 01822
city: Rajshahi
joined: 2026
fields 4
The loop reads every value with brackets, because key is a variable. member.key would look for a key literally named "key". The joined key was added after the literal, so it prints last.
Two objects can look identical and still be two boxes. This program compares references, then contents.
const a = { city: "Dhaka" };
const b = { city: "Dhaka" };
const c = a;
console.log(a === b);
console.log(a === c);
console.log(a.city === b.city);
c.city = "Comilla";
console.log(a.city, b.city);
false
true
true
Comilla Dhaka
a and b were built by two literals, so they are two boxes, and === says so. c is a second name for a's box. Comparing what is inside means comparing key by key, which Lesson 06 turns into a full deep equality.
Where this is used
- GitHub REST API: every response, such as a repository or a user, is a JSON object with keys like
nameandid. - npm: a project's
package.jsonis one object whose keys, such asname,versionanddependencies, Node and npm read directly. - Node.js:
process.envis an object whose keys are the environment variables, read asprocess.env.PORTorprocess.env[name].
Common mistakes
1. A dot where brackets belong
const prices = { tea: 20, coffee: 60 };
const item = "coffee";
console.log(prices.item);
console.log(prices[item]);
undefined
60
prices.item looks for a key literally named "item", which does not exist, so it prints undefined. The dot never reads a variable. When the key is in a variable, use prices[item].
2. A missing comma between properties
const member = { name: "Rafi" city: "Dhaka" };
console.log(member.city);
Node refuses to run the file and says SyntaxError: Unexpected identifier 'city'. After the first value, the parser expects a comma or the closing brace. A space does not separate two properties.
3. Expecting a copy from an assignment
const original = { stock: 10 };
const backup = original;
original.stock = 0;
console.log(backup.stock);
0
The backup was meant to keep 10, but it prints 0. backup = original copied the arrow, so both names point at one box. To keep a separate copy, build a new box: const backup = { ...original };.
Amara runs the members' page of a chess club. A member's profile is a set of fields, such as a name, a city and a motto. Each edit sets one field. The first edit of a field adds it, and a later edit of the same field replaces its value. Amara wants the profile after every edit.
Input. The first line holds n. Each of the next n lines is one edit: a field name, one space, then the value. A value can contain spaces.
Output. First, the number of fields in the final profile. Then one line per field, in the order the fields were first added: the field name, a colon, a space and its last value.
Constraints. 1 <= n <= 1000; Each field name is 1 to 8 lowercase letters.; Each value is 1 to 30 letters, digits and spaces, and it never starts or ends with a space..
Sample.
5
name Rafi Hasan
city Sylhet
motto Think twice
city Dhaka
rating 1450
4
name: Rafi Hasan
city: Dhaka
motto: Think twice
rating: 1450
const input = require("fs").readFileSync(0, "utf8");
const lines = input.split("\n");
let at = 0;
const next = () => lines[at++];
const nextInt = () => Number(next());
const out = [];
// your code: read with next() and nextInt(), push every line of output to out
console.log(out.join("\n"));
Run in Compiler
Graded as profile-edits, a free problem in this module's problem set.
Tanvir runs a small guest house. Each check-in puts a guest in a room, and a later check-in to the same room replaces the earlier guest. At night he asks who is in some rooms.
Input. n, then n check-ins, each a room and a guest's name. Then q, then q rooms to ask about.
Output. one line per question: the guest in that room, or vacant if nobody checked in there.
Constraints. 1 <= n, q <= 1000. Rooms and names are 1 to 10 letters or digits.
Sample. the input below.
4
r101 Nadia
r102 Sumon
r101 Kenji
r205 Lima
3
r101
r103
r205
const input = require("fs").readFileSync(0, "utf8");
const tokens = input.split(/\s+/).filter(Boolean);
let at = 0;
const next = () => tokens[at++];
const nextInt = () => Number(next());
const out = [];
// your code: read with next() and nextInt(), push every line of output to out
console.log(out.join("\n"));
Sample output.
Kenji
vacant
Lima
Run in Compiler
Maria wants to check her picture of references. She writes tiny scripts with four commands. new x points the name x at a new empty box. alias y x points y at the box x points at. set x k v writes key k with value v into x's box. get x k prints that key's value, or none.
Input. n, then n commands, one per line, in the form above.
Output. one line per get command.
Constraints. 1 <= n <= 1000. Names, keys and values are 1 to 8 lowercase letters or digits. Every name is created by new or alias before it is used.
Sample. the input below.
7
new a
alias b a
set b city dhaka
new c
set c city khulna
get a city
get c city
const input = require("fs").readFileSync(0, "utf8");
const tokens = input.split(/\s+/).filter(Boolean);
let at = 0;
const next = () => tokens[at++];
const nextInt = () => Number(next());
const out = [];
// your code: read with next() and nextInt(), push every line of output to out
console.log(out.join("\n"));
Sample output.
dhaka
khulna
Run in Compiler
Common doubts
Is an array an object too?
Yes. An array is an object whose keys are the indexes, plus a special
length. That is whyconstarrays behave likeconstobjects. Module 7's Lesson 05 showed the details.Do I need quotes around keys?
Not for plain words like
nameorcity. You need them for keys with spaces or dashes, such as{ "first name": "Rafi" }. Such a key can only be read with brackets.Why does
console.logof an object show it differently from my literal?Node prints objects in its own readable format, with spaces after the braces. It is a picture of the object, not the code you wrote. Lesson 02 shows
JSON.stringify, which prints a fixed text form.When should I use an object instead of an array?
Use an array for a list of things of the same kind, in order. Use an object for one thing with named parts, like a member with a name and a city. Lesson 04 makes this a full decision rule.
Key takeaways
- An object holds values under string keys, written
{ key: value }. - Read with
o.keywhen you know the key, ando[name]when it is in a variable. - Assigning a new key adds it at the end; assigning an existing key replaces its value in place.
- A variable holds a reference to an object;
p = ogives the same box a second name. constfixes the reference, not the box;{ ...o }builds a new box.- Go deeper: Under the Hood, Property Order, Hidden Classes, Inline Caches and the Cost of delete (Pro).
Next: every operation on objects, one by one, with what it returns and whether it changes the object.
End of lesson 1
Mark it done, and your progress moves with you.
Next: Every Object Operation, One by One: Keys, Copies, Destructuring and JSON