- Learn
- Computer Science & IT
- Programming
- JavaScript Programming
- Objects and JSON
Objects and JSON
BeginnerLesson 4 of 9 in JavaScript Programming Course
What you will learn
- Describe real things in code as objects with named properties, read and change those properties, and understand how objects are stored, copied and compared.
- Create objects and work with their propertiesWrite object literals, read properties with dot and bracket notation, and add, change and delete properties.
- Check and loop over an object's propertiesTest whether a property exists, list keys, values and entries, and loop over or transform an object's properties.
- Understand references, copying and comparisonExplain why objects are shared by reference, compare objects correctly, and make shallow and deep copies.
- Work with nested dataRead and process objects inside objects and arrays of objects, guard against missing parts with ?. and ??, and choose a structure that fits the data.
- Create objects and work with their properties
- Write methods that use their object's data, explain how the this keyword gets its value in each kind of call, and keep the right this when a method is passed around.
- Write methods that use their object's dataDefine methods with the shorthand syntax, use this to read and update the object, and call methods correctly.
- Explain how this gets its valueWork out the value of this from the way a function is called, and explain why arrow functions behave differently.
- Keep this when passing methods as callbacksRecognize when a method loses its object, and fix it with an arrow wrapper, bind, call or apply.
- Write methods that use their object's data
- Pull values out of objects and arrays with destructuring, and copy, merge and collect values with the spread and rest syntax.
- Destructure objectsUnpack object properties into variables, with renaming, default values, nested patterns and destructured function parameters.
- Destructure arrays and other iterablesUnpack values by position, skip and default them, swap variables, and destructure entries in loops.
- Copy, merge and collect with spread and restUse spread to copy and merge objects and arrays and to pass array items as arguments, use rest to collect values, and tell the two apart.
- Destructure objects
- Use Map for key-value collections with keys of any type and Set for collections of unique values, and choose between them and plain objects and arrays.
- Store key-value pairs in a MapCreate and update a Map, iterate over its entries, convert between Maps and objects, and decide when a Map fits better than an object.
- Keep unique values in a SetCreate and update a Set, remove duplicates from arrays, and compare collections with set operations.
- Store key-value pairs in a Map
- Read and write the JSON text format, turn JavaScript values into JSON with JSON.stringify and JSON text back into values with JSON.parse, and handle the cases where the two do not match.
- Read and write the JSON formatRecognize valid JSON, explain how it differs from a JavaScript object literal, and spot common syntax errors.
- Turn values into JSON text with JSON.stringifyConvert objects and arrays into JSON strings, format the output, choose which properties to include, and predict what gets dropped or changed.
- Turn JSON text into values with JSON.parseParse JSON safely, restore values such as dates with a reviver, check the parsed data, and round-trip data through JSON.
- Read and write the JSON format
Objects and JSON study guide
1Model data with objects
An object keeps related values together under names, so a record can describe one book, person, or reading. Working with those names lets you change one field, examine a whole record, and organize lists of records. You will also trace which variables point to the same object. That distinction matters whenever a change appears in two places, a copy contains nested data, or a missing field needs a sensible default.
1.1 Create objects and work with their properties
An object groups related values under names. Each property pairs a key with a value. Use one object for a record and an array of objects for an ordered list. Parallel arrays can lose their alignment when only one is sorted or changed.
Create and read a record
const book = { title: "Dune", pages: 412, available: true };
book.title; // "Dune"
book["title"]; // "Dune"
Property values may be arrays, objects, or functions as well as primitive values. Keys are strings or symbols; these examples use strings. String keys that are valid identifiers need no quotes. Keys containing spaces or hyphens need quotes: { "release-year": 1965 }. Numeric keys become strings, so { 1: "a" } has key "1". Object literals permit a trailing comma.
Dot access uses the literal name after the dot. Bracket access evaluates an expression:
const field = "pages";
book[field]; // 412
book.field; // undefined
The second expression looks for a property named "field". Brackets also handle book["release-year"]. A missing property returns undefined; continuing through it, as in book.author.name, throws a TypeError when author is missing.
Change the object's contents
book.author = "Frank Herbert";
book.pages = 896;
delete book.available;
Assignment adds a missing property or replaces an existing value. Deletion removes the property itself, so "available" in book is now false. const keeps the variable attached to this object but permits changes to its contents.
For const reading = { city: "Nairobi", tempC: 24.5 };, reading.tempC += 1.5; stores 26. Then reading.humidity = 61; adds a field. With const key = "humidity";, reading[key] reads 61. A name such as tempC makes the measurement's unit visible.
Use variables in a literal
const name = "Lena";
const age = 29;
const person = { name, age };
const lang = "nl";
const greeting = { [lang]: "Hallo" };
Shorthand { name, age } means { name: name, age: age }. A computed name evaluates the expression inside square brackets: greeting has property "nl". Without those brackets, { lang: "Hallo" } would create the literal key "lang".
Key Takeaways
- An object groups related values into named properties.
- Use brackets for a key stored in a variable or a key containing a hyphen.
- Assignment adds or replaces a property;
deleteremoves it. constfixes the variable, not the object contents.- Shorthand takes a variable value; a computed key evaluates an expression.
1.2 Check and loop over an object's properties
Checking whether a field exists is different from checking its value. An empty string, zero, or false may be a perfectly valid stored value.
Ask the right existence question
const item = { stock: 0, note: undefined };
Object.hasOwn(item, "stock"); // true
"note" in item; // true
Boolean(item.stock); // false
Object.hasOwn(obj, key) checks only the object's own properties. The in operator also checks inherited properties: "toString" in {} is true, while Object.hasOwn({}, "toString") is false. Older code often calls obj.hasOwnProperty(key).
A condition such as if (item.stock) tests truthiness. It cannot distinguish a missing field from a present field holding zero. The same issue applies to "", false, null, undefined, and NaN. Even a value of undefined can belong to an existing property, as note does above.
Choose keys, values, or pairs
For const scores = { Ana: 82, Ben: 74, Chen: 91 };, these functions return arrays of own enumerable string-keyed properties:
| Expression | Result |
|---|---|
Object.keys(scores) | ["Ana", "Ben", "Chen"] |
Object.values(scores) | [82, 74, 91] |
Object.entries(scores) | [["Ana", 82], ["Ben", 74], ["Chen", 91]] |
The object has no array-style length: scores.length is undefined. Object.keys(scores).length counts three properties. To total the scores, reduce the values with an initial zero. Their sum is 247 and their mean is about 82.33.
Loop with both parts available
let bestName;
let bestScore = -Infinity;
for (const [name, score] of Object.entries(scores)) {
if (score > bestScore) {
bestName = name;
bestScore = score;
}
}
The loop compares 82, 74, then 91 with the best score seen so far. It finishes with "Chen" and 91. The square-bracket pattern gives names to each pair's two parts.
A plain object is not iterable, so for...of scores throws a TypeError. Object.entries(scores) supplies an iterable array. The older for...in loop produces string keys and can include inherited enumerable properties.
Transform entries back into an object
const weightsG = { flour: 500, sugar: 250, salt: 5 };
const weightsKg = Object.fromEntries(
Object.entries(weightsG).map(([item, g]) => [item, g / 1000])
);
entries produces pairs, map changes each value while keeping its key, and fromEntries rebuilds the record. The result is { flour: 0.5, sugar: 0.25, salt: 0.005 }; weightsG stays unchanged.
Own property order is defined. Array-index keys from "0" through "4294967294" appear first in ascending order; other string keys follow insertion order. A leading zero, as in "01", prevents a key from being an array index. Thus Object.keys({ b: 1, 2: "x", a: 2, 1: "y" }) gives ["1", "2", "b", "a"]. Use an array when positions carry meaning.
Key Takeaways
- Property existence and truthiness answer different questions.
Object.hasOwnchecks own properties;inalso checks inherited ones.Object.entriesprovides key-value pairs for loops and transformations.Object.fromEntriesrebuilds an object from pairs.- Array-index keys come before other string keys in property order.
- Count own enumerable string keys with
Object.keys(obj).length.
1.3 Understand references, copying and comparison
Two variables can refer to one object. Before predicting an update, decide whether the code created a new object or copied a reference to an existing one.
Trace identity separately from data
const p1 = { x: 1 };
const p2 = p1;
p2.x = 99;
p1.x; // 99
p1 === p2; // true
Assignment copied the reference. Both names reach the same property. Primitive assignment works differently: after let a = 5; let b = a; b = 6;, a remains 5.
Objects compare by identity with both === and ==. Two literals create separate objects even when all fields match: ({ x: 1 } === { x: 1 }) is false. To compare their data, compare the relevant properties. A matching printed appearance is not an identity check.
Passing an object into a function also copies its reference. A function that runs order.status = "shipped"; changes the caller's object. Returning { ...order, status: "shipped" } instead creates a new top-level record.
Separate const, freezing, and copying
const cart = { items: 2 }; allows cart.items = 3; but reassigning cart = {}; throws a TypeError. Object.freeze(cart) blocks changes to its own properties. In strict mode, attempts to add, remove, or overwrite them throw a TypeError.
Freezing is shallow. If a frozen record holds another object, that nested object's fields can still change. A frozen container and a deeply unchangeable structure are different things.
Follow a shallow copy
const settings = { theme: "dark", font: { size: 14 } };
const copy = { ...settings };
copy.theme = "light";
copy.font.size = 18;
Spread creates a new outer object. Its theme value is copied, so changing copy.theme leaves settings.theme as "dark". Its font value is a reference, copied into both records. Updating copy.font.size therefore makes settings.font.size equal 18 too.
Object.assign({}, settings) also makes a shallow copy. A shallow copy separates the outer record while retaining the identity of nested objects and arrays.
Copy nested data when it must be independent
const deep = structuredClone(settings);
deep.font.size = 22;
settings.font.size; // 18
structuredClone copies nested objects and arrays, along with supported values such as Map, Set, and Date. It also handles circular references. The new nested font can change independently. Functions cannot be cloned this way: an object containing a method causes a DataCloneError.
A JSON round trip, JSON.parse(JSON.stringify(obj)), copies only JSON-compatible data. It drops object properties holding undefined or functions, changes dates into strings, and loses Map and Set entries unless you convert them. Choose a clone for independent supported JavaScript data, and choose JSON when you intend to create a text representation.
Key Takeaways
- Assignment and function calls copy object references.
- Objects compare by identity, even when their fields match.
- A shallow copy shares nested objects.
Object.freezeblocks own-property changes but is shallow.structuredClonecopies supported nested data and rejects functions.
1.4 Work with nested data
Nested data combines records and lists. Read a path one step at a time, checking whether each step reaches an object, an array, or a final value.
Read and process a list inside a record
const order = {
id: 1042,
customer: { name: "Sara", city: "Lisbon" },
items: [
{ product: "Tea", qty: 2, unitPrice: 4.5 },
{ product: "Mug", qty: 1, unitPrice: 9 }
]
};
order.customer.city reads "Lisbon". order.items[1].product first selects the items array, then its second record, then "Mug". order.items.length is 2.
The array methods you already know work with record properties. map(item => item.product) produces product names. filter(item => item.qty > 1) keeps the tea line. find(item => item.product === "Mug") returns the existing mug record, so changing that returned record changes the item in the order.
To calculate the total, use order.items.reduce((sum, item) => sum + item.qty * item.unitPrice, 0). The first line adds 9; the second adds 9 more, producing 18. Computing a total when needed prevents a separately stored total from becoming stale when items change.
Read optional data with a fallback
order.shipping?.date; // undefined
order.customer?.city ?? "unknown"; // "Lisbon"
Optional chaining, ?., stops when the value immediately to its left is null or undefined. A direct order.shipping.date would throw a TypeError. Use a check at each optional link in a longer path. Bracket access and calls also support it: order.items?.[0] and order.print?.().
Optional chaining is for values allowed to be missing. Required fields still need checks or errors that reveal a broken record. It cannot appear on the left of assignment: order.shipping?.date = "2026-05-01" is a syntax error.
The nullish coalescing operator, ??, uses its right side only for null or undefined. It preserves zero, false, and "". For const qty = 0;, qty ?? 1 remains zero, while qty || 1 becomes 1 because || replaces every falsy value.
Pick a useful shape
An array of records suits listing, filtering, and ordering. An object keyed by ID supports direct lookup: usersById["u23"].name. A Map is useful when lookup keys are frequently added and removed.
Newer JavaScript offers Object.groupBy, widely available since 2024:
const tasks = [
{ title: "Book flights", status: "done" },
{ title: "Pack", status: "todo" },
{ title: "Renew passport", status: "done" }
];
const grouped = Object.groupBy(tasks, task => task.status);
grouped.done holds two records and grouped.todo holds one. The result has no prototype, so it has no inherited toString method. An older alternative builds the arrays explicitly for these status keys:
const groupedOlder = tasks.reduce((groups, task) => {
if (!Object.hasOwn(groups, task.status)) {
groups[task.status] = [];
}
groups[task.status].push(task);
return groups;
}, {});
The first record in each group creates its array. Later records append to that same array. This accumulator is an ordinary object; the newer method's result has no prototype. Use consistent names and value types across records; names such as distanceKm and tempC also keep units visible.
Key Takeaways
- Read a nested path from its outer object toward the needed value.
findreturns the existing record, not a copy.?.stops at a nullish link;??supplies a nullish fallback.- Use an array for an ordered list and keyed data for direct lookup.
Object.groupBygroups records into arrays under keys.- Consistent field names, types, and units make records easier to process.
2Give objects behavior with methods and this
Methods let an object combine its data with actions that use that data. The small word this connects an action to an object, but its value depends on how the function runs. You will follow ordinary method calls, borrowed methods, and callbacks, then choose a wrapper or a bound function when an action must keep its object. The examples run as modules, so strict mode applies unless an example explicitly uses non-strict code.
2.1 Write methods that use their object's data
A method is a function-valued property called through an object. It lets an action use the record's current data.
Write a method with this
const counter = {
count: 0,
increment() {
this.count += 1;
return this.count;
}
};
counter.increment(); // 1
counter.increment(); // 2
The shorthand increment() { ... } defines a regular function-valued property. In counter.increment(), the receiver counter becomes this. Each call reads the stored count, adds one, and returns the new number.
A bare count selects a variable, not the property. Without a matching variable, count += 1 throws ReferenceError. Use this.count to reach the receiver's field.
Read a function or run it
counter.increment reads a value of type "function". Parentheses in counter.increment() run it and give its return value. When passing an action for later use, the receiving code needs the function, not the result of executing it now.
Built-in methods use this calling shape too: Math.max(3, 7) returns 7 and "abc".toUpperCase() returns "ABC".
Reuse an action
function describe() {
return this.name + " (" + this.year + ")";
}
const film = { name: "Up", year: 2009, describe };
const song = { name: "Hello", year: 2015, describe };
film.describe() returns "Up (2009)"; song.describe() returns "Hello (2015)". Using this lets the same function read either receiver. Hard-coding film.name would tie it to the film variable.
Return the receiver for another action
const playlist = {
tracks: [],
add(title) { this.tracks.push(title); return this; },
count() { return this.tracks.length; }
};
playlist.add("Rain").add("Waves").count(); // 2
Each add returns the playlist, allowing the next method lookup on that object. Returning a number from add would break the chain because the next add would be looked up on that number.
Ordinary JSON conversion omits methods: stringifying { name: "Kit", hi() { return 1; } } writes only name. Parsing cannot restore omitted code. Records for saving or exchange often keep plain data and use separate processing functions.
Key Takeaways
- A method is a function-valued property called through an object.
- In
obj.method(), a regular method usesobjasthis. - A function value and the result of calling it are different values.
- Returning
thiscan let another method call follow. - Ordinary JSON conversion omits function-valued properties.
2.2 Explain how this gets its value
For a regular function, the call determines this. Storing a function in an object does not permanently attach it to that object.
Follow the receiver at the call
const a = { n: "A", who() { return this.n; } };
const b = { n: "B" };
b.who = a.who;
a.who(); // "A"
b.who(); // "B"
Both properties hold the same function. The receiver before the dot differs, so the results differ. The function's original location does not decide which object it reads.
For a plain call, f(), strict mode supplies undefined as this. JavaScript modules always run in strict mode; a script or function can also begin with "use strict";. A non-strict regular function called plainly uses the global object, globalThis, instead. In a browser that global is window.
Recognize an arrow's surrounding context
An arrow has no this of its own. It keeps the this from the surrounding code where it was created. An object literal does not create a new this context.
// At the top level of a module:
const user = {
name: "Ida",
hi: () => "Hi, " + this.name
};
Calling user.hi() throws a TypeError: the arrow captured the module's top-level this, which is undefined. Changing the call to look like a method cannot give the arrow a new receiver. At the top level of a classic script, the surrounding this is the global object instead.
Use a regular method when the action needs the calling object. Use an arrow inside that method when a callback needs to keep the method's context:
const group = {
title: "Team",
members: ["Ana", "Ben"],
list() {
return this.members.map(m => this.title + ": " + m);
}
};
group.list(); // ["Team: Ana", "Team: Ben"]
list gets group from its method call. The arrow is created inside that running method and keeps the same this. A strict regular callback without a supplied receiver would receive undefined, so this.title would fail there.
Use a decision order
When you encounter a call, first identify the function being called:
- An arrow keeps its surrounding
this;call,apply, andbindcannot replace it. - A bound regular function keeps its bound
this, even throughcallorapply. - An unbound regular function called with
callorapplyuses the suppliedthis. - An ordinary
obj.method()call usesobj. - A plain regular call uses
undefinedin strict mode or the global object in non-strict mode.
Callbacks can have a receiver supplied by their caller. For example, a regular DOM event listener registered with addEventListener receives the element it is registered on as this. Read how the receiving code invokes a callback before assuming it is a plain call.
Key Takeaways
- A regular function gets
thisfrom its call, so borrowing a method changes its receiver. - A strict plain call uses
undefinedasthis. - An arrow keeps the surrounding
thiswhere it was created. - A bound receiver takes precedence over later
callorapply. - An arrow inside a regular method can keep that method's
this.
2.3 Keep this when passing methods as callbacks
Taking a method out of its object preserves the function but loses the original calling expression. A callback must be called in a way that supplies the object it needs.
Diagnose a detached method
const counter = {
count: 0,
increment() {
this.count += 1;
return this.count;
}
};
const inc = counter.increment;
inc(); // TypeError in strict mode
inc() is a plain call, so this is undefined. Reading this.count fails before an increment can happen. In non-strict code, a plain call uses the global object and may write a global count instead; it does not reach the counter.
The same issue appears in array callbacks. [1, 2].map(counter.increment) supplies a function to map without its original receiver. The array values passed as arguments do not supply this.
Wrap the method call
const results = [1, 2].map(() => counter.increment());
// results is [1, 2]; counter.count is 2
The callback is an arrow that makes a normal counter.increment() call. That call has a receiver. The wrapper deliberately ignores the values passed by map; each invocation simply increments once. This form is convenient when only one use needs the callback.
Bind a reusable function
Starting with a fresh counter at zero:
const boundInc = counter.increment.bind(counter);
const results = [1, 2].map(boundInc);
// results is [1, 2]; counter.count is 2
bind returns a new function; it does not call the method yet. The new function keeps counter as its receiver. Binding it again with a different object cannot replace that receiver. Each call to bind creates a distinct function, so save the result when code must recognize the exact same callback later.
Binding can also fix leading arguments. function multiply(a, b) { return a * b; } followed by const double = multiply.bind(null, 2); makes double(21) return 42. null fills the receiver position because this function does not use this; 2 fills its first ordinary parameter.
Run once with call or apply
function describe(unit) {
return this.value + " " + unit;
}
describe.call({ value: 12 }, "kg"); // "12 kg"
describe.apply({ value: 12 }, ["kg"]); // "12 kg"
Both forms execute immediately with the chosen receiver. call supplies arguments separately; apply supplies them in an array. An arrow's context is fixed by its surroundings, so these methods cannot replace its this.
Passing counter.increment() as a callback runs the method immediately and passes its returned number. That is not the function map needs. In an event registration such as button.addEventListener("click", counter.increment()), the counter changes immediately and the numeric listener causes a TypeError. Pass a function value, an arrow wrapper, or a stored bound function.
Key Takeaways
- Detaching a method removes its original receiver.
- An arrow wrapper can make the required method call later.
bindcreates a function with a fixed receiver and optional leading arguments.callandapplyrun immediately; their argument formats differ.- Callback arguments must be functions, not the numeric result of running a method.
3Unpack and combine data with destructuring, spread and rest
Destructuring gives names to selected pieces of an object or an ordered collection. Spread builds values from existing parts, while rest gathers the pieces left over. These forms often appear together in functions that receive options or return updated records. Reading their position carefully lets you predict which variables exist, which defaults apply, and whether nested data is shared with the original.
3.1 Destructure objects
Object destructuring reads named fields into local variables. It selects values without copying the whole source record.
Match and rename
const book = { title: "Dune", pages: 412 };
const { pages, title: bookTitle, rating: stars = 0 } = book;
Matching uses property names, not their order. This declares pages as 412, bookTitle as "Dune", and stars as 0. It declares no title or rating variable: the name after a colon is the local target.
A missing field supplies undefined. A default replaces only undefined, preserving null, zero, false, and empty strings. const { a = 5 } = { a: null }; therefore leaves a as null.
Follow nested fields
const data = {
location: { name: "Oslo" },
current: { tempC: -3, windKph: 18 }
};
const {
location: { name },
current: { tempC, windKph: wind }
} = data;
The declared variables are name, tempC, and wind, holding "Oslo", -3, and 18. The enclosing location and current names select paths; they do not also become variables.
A missing outer object makes a nested read fail. const { shipping: { date } } = {}; throws TypeError. Supply an outer default when that field may be undefined:
const { shipping: { date } = {} } = {};
// date is undefined
This does not replace shipping when its value is null. Destructuring null or undefined directly also throws, so the source must be usable before unpacking begins.
Receive named options
function formatUser({ name, age = "unknown" }) {
return name + ", " + age;
}
formatUser({ name: "Mo" }) returns "Mo, unknown". Reordering the fields in { age: 40, name: "Ines" } still returns "Ines, 40". Calling formatUser with no argument fails because there is no object to unpack. A whole-parameter default permits that call:
function greet({ name = "guest" } = {}) {
return "Hi, " + name;
}
greet(); // "Hi, guest"
The outer default handles an omitted argument; the inner default handles a missing name. Passing null still fails. To assign existing variables, wrap an object-pattern statement in parentheses: let x, y; ({ x, y } = { x: 4, y: 7 });. Otherwise the initial braces are read as a statement block.
Key Takeaways
- Object patterns match properties by name.
- A colon renames the variable; it does not change the source key.
- Defaults replace
undefined, including missing properties, and preservenull. - Nested patterns need a usable object at every level.
- A whole-parameter default and a property default handle different missing values.
- Parentheses let an object pattern assign to existing variables.
3.2 Destructure arrays and other iterables
Array destructuring reads values by position from an iterable. Commas control which values are selected; the variable names do not choose positions.
Select or skip positions
const medals = ["gold", "silver", "bronze"];
const [first, second] = medals;
const [, , third] = medals;
The variables receive "gold", "silver", and "bronze". Empty slots consume values without declaring variables. A name beyond the available values receives undefined. Defaults replace only undefined:
const [p, q = 10] = [1]; // p is 1; q is 10
const [r = 10] = [null]; // r is null
Gather the remaining values
const [head, ...tail] = [1, 2, 3, 4];
// head is 1; tail is [2, 3, 4]
The final rest element makes a new array, empty when nothing remains. It must be last. Any object values inside that array keep their identities; collecting them does not deeply clone them.
Read a returned pair or swap variables
function minMax(nums) {
return [Math.min(...nums), Math.max(...nums)];
}
const [lo, hi] = minMax([4, 9, 1]);
// lo is 1; hi is 9
The caller follows the order agreed by the returning function. For many result fields, named object properties avoid having to remember positions.
With let x = 1, y = 2;, [x, y] = [y, x]; swaps the variables. The right-hand array is evaluated as [2, 1] before either assignment, preserving both old values without a temporary variable.
Unpack other iterables
Strings and Sets can be unpacked too: const [initial] = "Zoe"; gives "Z". Sets yield insertion-order values. Map entries are ordered pairs:
const distances = new Map([["park", 2], ["lake", 5]]);
for (const [place, distanceKm] of distances) {
console.log(place, distanceKm);
}
The loop receives a pair, then the pattern separates its key and value. Object.entries supplies the same pair shape.
A plain object is not iterable, so const [name] = { name: "Ana" }; throws TypeError. Use curly braces to select its named field: const { name } = { name: "Ana" };. Square brackets require a sequence of values; braces select properties by name.
Key Takeaways
- Array patterns match positions, and empty slots skip values.
- A missing position becomes
undefinedunless a default replaces it. - A final rest element collects remaining values into a new array.
- Array patterns also work on strings, Sets, and Map entries.
3.3 Copy, merge and collect with spread and rest
The three dots, ..., have two related jobs. Spread expands existing data into a value or call. Rest gathers values into a variable where a pattern or parameter declares it.
Merge in the order of priority
const defaults = { lang: "en", units: "metric", alerts: true };
const user = { lang: "pt", alerts: false };
const options = { ...defaults, ...user };
// { lang: "pt", units: "metric", alerts: false }
Object spread copies own enumerable properties. When a key appears again, the later value wins. Placing user before defaults would overwrite the user's settings. Spreading null or undefined into an object adds nothing.
Spread copies are shallow. If a nested record changes, copy that level too:
const settings = { theme: "dark", font: { size: 14, family: "serif" } };
const next = {
...settings,
font: { ...settings.font, size: 18 }
};
The outer spread preserves theme. The inner spread preserves family while replacing size. settings.font.size stays 14 because the changed nested record is new.
Replace one record in a list
const todos = [{ id: 1, done: false }, { id: 2, done: false }];
const updated = todos.map(t =>
t.id === 2 ? { ...t, done: true } : t
);
map makes a new array. The matching item becomes a new object, so updated[1].done is true while todos[1].done stays false. The unchanged first item is reused: updated[0] === todos[0] is true. Independent updates do not require copying unchanged records.
Expand iterable values
In an array literal or function call, spread takes values from an iterable. [...a, ...b] joins their values into a new array. Math.max(...[4, 9, 1]) passes three arguments and returns 9. A plain object cannot be expanded into an array: [...{ a: 1 }] throws a TypeError. Object spread and iterable spread use different rules for what they read.
Gather arguments or remaining properties
function total(...nums) {
return nums.reduce((sum, n) => sum + n, 0);
}
total(2, 3, 5); // 10
total(); // 0
A rest parameter receives a real array, even when it receives no values. It must be the final parameter, and there can be only one. The older arguments object is not a real array. Arrows have no own arguments, though they can read an enclosing regular function's binding.
Object rest collects own enumerable properties not already selected:
const userRecord = { id: 8, name: "Rae", password: "x9!k" };
const { password, ...publicUser } = userRecord;
// publicUser is { id: 8, name: "Rae" }
This creates a new record and leaves the source unchanged. The rest property must be last. Look at placement to distinguish the jobs: three dots in a literal or call spread outward; three dots in a parameter list or destructuring target gather inward.
Key Takeaways
- Later properties win when object spreads overlap.
- Copy each nested level that must change independently.
- Array and call spread need an iterable.
- Rest collects remaining arguments, items, or properties and must be last.
- A map-based update can copy a changed record while reusing unchanged records.
- Rest parameters are real arrays; the older
argumentsobject is not.
4Store collections with Map and Set
Some collections need keys that are objects or numbers, and others need to keep each value only once. Map and Set express those needs directly. You will create entries, test membership, follow insertion order, and convert collections to arrays or objects when another operation needs that shape. Equality still matters: two records with matching fields can remain two separate values in either collection.
4.1 Store key-value pairs in a Map
A Map stores associations between keys and values. Unlike a plain object's string keys, a Map key can be a number, an object, or another value without being converted into a string.
Create and maintain entries
const stock = new Map();
stock.set("apples", 12).set("pears", 7);
stock.get("apples"); // 12
stock.has("kiwis"); // false
stock.size; // 2
set adds an entry or replaces its value and returns the Map for chaining. get returns undefined for a missing key; use has to distinguish that from an entry storing undefined. delete(key) returns whether it removed an entry, and clear() empties the collection.
Bracket notation is a different storage area: stock["plums"] = 5; adds an ordinary property to the Map object. It does not create an entry visible to get, has, or size. Use the Map methods for entries.
Keep key types and identities
m.set(1, "number"); m.set("1", "text"); creates two entries. Plain object keys 1 and "1" would refer to one property. Map keys compare like ===, except NaN matches itself and signed zeros share a key.
const person = { id: 1 };
const notes = new Map([[person, "ready"]]);
notes.get(person); // "ready"
notes.get({ id: 1 }); // undefined
The second lookup creates a new object. Matching fields do not make it the same key. Keep and reuse the original object reference when using an object as a key.
Iterate and convert pairs
for (const [name, count] of stock) {
console.log(name, count);
}
A Map yields [key, value] pairs in first-insertion order. Replacing a value does not move its key. keys(), values(), and entries() return iterable views you can loop over or spread into arrays. forEach passes its callback the value first, then the key.
new Map(Object.entries(obj)) turns a record's entries into a Map. Object.fromEntries(map) creates an object; use it when keys are strings, because object keys cannot preserve every Map key type. [...map] creates an array of pairs, useful for saving a Map's entries as JSON.
Count changing keys
const words = "the cat saw the dog and the bird".split(" ");
const counts = new Map();
for (const word of words) {
counts.set(word, (counts.get(word) ?? 0) + 1);
}
The first occurrence starts at zero, then adds one. Later occurrences read and increase the stored count. counts.get("the") is 3 and counts.size is 6: the size counts distinct words, not all eight occurrences.
Choose a plain object for a record with known fields, especially JSON-friendly data. Choose a Map for changing keys, frequent size checks, or keys with types that must be preserved. Newer Map.groupBy preserves group-key types:
const owner = { name: "Ari" };
const jobs = [{ owner, done: false }, { owner, done: true }];
const byOwner = Map.groupBy(jobs, job => job.owner);
byOwner.get(owner).length; // 2
In older code, loop over jobs. For each owner, use has and set to create a group array once, then get(owner).push(job) to add the record. Keeping owner as an object key distinguishes different owners even when their name fields match. A WeakMap can attach metadata to objects without keeping otherwise unreachable key objects alive. It has no iteration or size. Modern WeakMaps also accept non-registered symbols as keys.
Key Takeaways
- Map keys preserve their types and compare objects by identity.
- Use
set,get,has, anddeletefor entries, not bracket notation. sizecounts entries and iteration follows key insertion order.- Maps convert through key-value pairs.
- A WeakMap can hold object metadata without keeping key objects alive.
Map.groupBypreserves group-key types in a newer grouping API.
4.2 Keep unique values in a Set
A Set stores unique values. Its main questions are whether a value is present and how two collections overlap.
Add and test values
const tags = new Set(["js", "css", "js"]);
tags.size; // 2
tags.add("html");
tags.has("css"); // true
Adding an existing value changes nothing. add returns the Set for chaining. delete(value) returns whether the value was removed, while clear() removes all values. The constructor takes one iterable: use new Set([1, 2, 3]). new Set(1, 2, 3) throws a TypeError because its first input is not iterable. A string is iterable, so new Set("hello") contains "h", "e", "l", and "o".
A Set has no indexed positions. tags[0] is undefined. Loop with for...of, call forEach, or convert to an array for operations such as map and filter. Iteration follows insertion order; removing a value and adding it again places it at the end.
Remove duplicates without sorting
const unique = [...new Set([3, 1, 3, 2, 1])];
// [3, 1, 2]
The Set keeps the first occurrence of each value. Spreading yields those values into an array. Array.from(set) does the same conversion.
Values compare like ===, except NaN matches itself; signed zeros count as one value. Object values compare by identity: new Set([{ id: 1 }, { id: 1 }]).size is 2. To keep one record per ID, build a Map keyed by that ID, then read its values. Later records with the same ID replace earlier records, because set updates an existing entry.
For repeated membership tests in large collections, a Set avoids the one-by-one scan of an array's includes. Use it when presence matters more than position.
Compare two collections
Newer Set methods are available across major browsers from 2024. With a holding 1, 2, 3, 4 and b holding 3, 4, 5:
| Operation | Meaning | Resulting values |
|---|---|---|
a.union(b) | In either collection | 1, 2, 3, 4, 5 |
a.intersection(b) | In both | 3, 4 |
a.difference(b) | In a but not b | 1, 2 |
a.symmetricDifference(b) | In exactly one | 1, 2, 5 |
These return new Sets. isSubsetOf checks that every value is in the other collection. isSupersetOf checks the reverse containment. isDisjointFrom checks that the collections share no values. All three return booleans. For example, new Set([3, 4]).isSubsetOf(a) is true. Difference has a direction: values in b but not a would be only 5.
In an older environment, an intersection can be written as new Set([...a].filter(x => b.has(x))).
Ordinary JSON.stringify(set) produces "{}", losing the stored values. Convert to an array before writing JSON, then use new Set(parsedArray) when reading. A WeakSet can mark objects as processed without keeping otherwise unreachable objects alive. It cannot be iterated. Modern WeakSets also accept non-registered symbols.
Key Takeaways
- A Set stores each distinct value once and tests presence with
has. - Convert a Set to an array for indexing or array methods.
- Object values remain distinct unless their references match.
- Union, intersection, difference, and symmetric difference answer different membership questions.
- Difference depends on which Set comes first.
- Convert Set values to an array before writing JSON.
- WeakSet can mark objects without keeping them alive.
5Convert data to and from JSON
JSON carries structured data as text. Converting between that text and live JavaScript values is useful only when you know which information survives the conversion. You will recognize valid JSON, control how values are written, and handle malformed input when reading it. A successful parse also needs a shape check before the program relies on its fields; valid text alone cannot guarantee the expected data.
5.1 Read and write the JSON format
JSON, short for JavaScript Object Notation, is a language-independent text format for structured data. An object is a live JavaScript value; JSON is text that can describe such a value.
Recognize the six value kinds
JSON supports objects, arrays, strings, numbers, booleans, and null. Any of these can be the top-level value. The text 42 is valid JSON even without braces. JSON has no representation for functions, undefined, dates as a distinct type, Map, Set, NaN, or Infinity.
{
"name": "Amara",
"age": 34,
"languages": ["en", "yo"],
"address": null,
"member": true
}
The same structural ideas appear in JavaScript literals, but the allowed spelling is stricter. JSON is used in configurations and data exchange. Interchanged JSON uses UTF-8 encoding and the media type application/json; JSON files use the .json extension.
Apply the syntax rules
Every object key must be a string in double quotes. Strings also require double quotes. Commas separate properties or array items, but no comma may follow the last one. Comments are forbidden. Whitespace between values and punctuation is allowed.
| Text | Why it fails |
|---|---|
{name: "Amara"} | The key lacks double quotes. |
{'name': 'Amara'} | Single quotes are not JSON string delimiters. |
{"a": 1,} | The final comma has no following property. |
{"a": undefined} | Undefined is not a JSON value. |
Numbers use decimal notation. 1.5e3, -0.25, and 0 are valid. 07, +1, and 0x1F are invalid because JSON forbids leading zeros, a leading plus, and hexadecimal notation.
Escape characters inside strings
Use \" for a double quote, \\ for a backslash, \n for a newline, and \t for a tab inside a JSON string. A raw line break cannot appear inside a JSON string. Let JSON.stringify generate escapes from actual string values so embedded quotes cannot break the output.
A Unicode escape consists of a backslash, the letter u, and four hexadecimal digits for one UTF-16 code unit. For example, \u00e9 represents é. A character beyond that range may need two such escapes; it can also be written directly in the string. The quotes around JSON text in JavaScript source are a separate layer from the quotes inside the JSON itself.
Preserve identifiers and avoid duplicate keys
JavaScript numbers preserve every integer in the safe range from -9007199254740991 through 9007199254740991. Outside that range some integers remain exact and others round. Parsing '{"id":9007199254740993}' gives an id value of 9007199254740992. Store long identifiers as JSON strings when their exact digits matter.
Object keys should be unique within each JSON object. JavaScript's JSON.parse keeps the last occurrence when a key repeats: parsing '{"a":1,"a":2}' produces { a: 2 }. Distinct properties cannot be recovered from that repeated name.
Key Takeaways
- JSON is text with six value kinds, including primitives at the top level.
- Keys and strings need double quotes; comments and trailing commas are invalid.
- JSON numbers exclude hexadecimal notation, leading plus signs, and leading zeros.
- Escapes represent special characters inside strings.
- Use strings for long identifiers whose exact digits must survive.
- Repeated object keys are unsafe;
JSON.parsekeeps the last value.
5.2 Turn values into JSON text with JSON.stringify
JSON.stringify converts supported JavaScript values into JSON text. Decide what the text must preserve before converting a record with methods or collection values.
Write compact or indented text
const person = { name: "Amara", age: 34, tags: ["admin"] };
const text = JSON.stringify(person);
// '{"name":"Amara","age":34,"tags":["admin"]}'
The result is a string, so typeof text is "string". Own enumerable string-keyed properties are written in Object.keys order. JSON.stringify(person, null, 2) adds line breaks and two spaces of indentation per level. The second argument is null because no replacer is being used. Numeric indentation is capped at 10 spaces; a string used for indentation is limited to its first 10 characters.
Predict dropped and changed values
const data = {
a: undefined,
b: () => 1,
c: NaN,
d: [undefined, Infinity]
};
JSON.stringify(data); // '{"c":null,"d":[null,null]}'
An object property holding undefined, a function, or a symbol is omitted. In an array, such a value becomes null, preserving that position. NaN and Infinity become null in either place. At the top level, unsupported values such as undefined or a function make stringify return undefined, not JSON text.
A Date writes its ISO UTC string through toJSON. Ordinary Map and Set entries are not written, so either collection becomes {}. Convert Set values to an array, and Map entries to pairs or a string-keyed object, before writing them.
A circular reference and an ordinary BigInt such as 10n cause a TypeError. Shared references that do not form a cycle can be serialized, but JSON will not preserve their shared identity.
Choose data with a replacer
const user = { name: "Rae", email: "rae@example.com", password: "x9!k", age: 31 };
JSON.stringify(user, ["name", "email"]);
// '{"name":"Rae","email":"rae@example.com"}'
A replacer array selects property names. A replacer function transforms values as they are visited, beginning with the whole input under the key "":
JSON.stringify(user, (key, value) =>
key === "password" ? undefined : value
);
Returning undefined omits an object property, makes an array position null, or causes an overall undefined result when returned for the root. Return the unchanged value for data you want to keep.
An object's toJSON() method can choose its representation. For { celsius: 21, toJSON() { return { c: this.celsius }; } }, stringification writes '{"c":21}'.
Do not build JSON by joining quoted strings yourself: embedded quotes or newlines can break its syntax. Do not stringify already serialized text expecting an object representation either. Stringifying '{"a":1}' encodes a string; parsing that result once returns a string, not an object.
Key Takeaways
JSON.stringifyusually returns JSON text; top-level unsupported values can returnundefined.- Unsupported object properties disappear, while unsupported array items become
null. - Dates become strings; Map and Set need explicit conversion.
- Circular structures and ordinary BigInts throw
TypeError. - Replacers and
toJSONcontrol the written representation. - Stringifying JSON text again encodes a string, not its contained object.
5.3 Turn JSON text into values with JSON.parse
JSON.parse reads JSON text and returns the value it describes. Parsing checks text syntax; checking whether the result fits your program is a separate task.
Read the value, then inspect its fields
const data = JSON.parse('{"city":"Lima","tempC":19.5}');
data.tempC + 1; // 20.5
The outer single quotes delimit a JavaScript string. The double quotes inside it belong to JSON. Parsing creates a plain object with a numeric tempC property. A text describing an array produces an array; primitive JSON texts produce their matching primitive values.
Data that is already an object needs no parse step. JSON.parse({ a: 1 }) first converts that argument to "[object Object]", then throws a SyntaxError. JSON.parse(undefined) also fails because "undefined" is not valid JSON text.
Handle invalid text
function safeParse(text, fallback) {
try {
return JSON.parse(text);
} catch (error) {
return fallback;
}
}
safeParse("{ok:true}", null); // null
Malformed input, including an empty string, single-quoted JSON strings, unquoted keys, and trailing commas, throws a SyntaxError. Catching that failure gives the program a deliberate fallback instead of stopping it. A successfully parsed null is also possible, so choose how the caller will distinguish a fallback from valid data when that difference matters.
Restore selected values with a reviver
A date stored as text returns as a string. An array of key-value pairs remains an array until new Map(pairs) turns it into a Map. Functions omitted during writing cannot be recovered from the remaining text.
const text = '{"title":"Dentist","due":"2026-03-01T09:00:00.000Z"}';
const event = JSON.parse(text, (key, value) =>
key === "due" ? new Date(value) : value
);
The reviver visits inner values before their containers, ending with the whole result under key "". Returning a value replaces the current value. Returning undefined removes that property; returning it for the root makes the entire parse result undefined. Forgetting return value for other keys can therefore erase the data you meant to keep.
Check shape and plan a round trip
Valid JSON can still have the wrong types. Parsing '{"temp":"19"}' leaves a string, so data.temp + 1 would concatenate to "191". Check required fields and types, such as typeof data?.temp === "number", before calculating. Convert with Number only when the intended format permits a numeric string; optional chaining and ?? can handle fields that may be absent.
For const settings = { theme: "dark", volume: 0.8, recent: ["a.txt"] };, stringification produces 48 characters of JSON text. Parsing it restores the same field data, but loaded === settings is false because parsing creates new objects. A Date returns as a string, an undefined-valued object property disappears, and an unconverted Set returns as {}. Convert special values before writing and restore their intended types after reading.
Key Takeaways
JSON.parsereads text and throwsSyntaxErrorfor malformed JSON.- Successful parsing does not check the required fields or their types.
- A reviver visits children first and must return values you want to keep.
- JSON round trips create new objects and preserve only representable data.
- Restore collection and date types explicitly after parsing.
Objects and JSON examples
1Follow a shipment record
16 steps
- 1IntroductionBuild a record you can inspect
A shipment combines an ID, a destination, and package records. You will build it, inspect its fields, and trace shared and copied data. Two names can hold one reference to the same record, and a shallow copy still shares nested levels, so an update can reach data you meant to keep apart.
The shipment examples continue across slides unless a step explicitly creates a separate object.
- 2ExampleCreate the shipment
const shipment = { id: "s12", destination: "Porto", delayMin: 0 };The literal creates three properties. Delay is measured in minutes, including a valid zero.
shipment.destinationreads"Porto". Const permits property updates. - 3ExampleChoose a field while the program runs
const field = "destination"; shipment[field] = "Braga"; shipment.checked = true;Bracket access evaluates field and updates the destination property. Assignment also adds checked because it did not exist before. The shipment still has the same identity; neither operation replaces the outer object. Writing
shipment.fieldwould instead create a separate property literally named"field". - 4ChallengeRead a computed name
What does
shipment[field]read now, and does the shipment own a field property?---reveal---
It reads
"Braga". The variable field contains the key"destination", so both the update and the read reach that property.Object.hasOwn(shipment, "field")is false because no literal field property was created. A variable used to choose a property does not itself become part of the record. - 5ExplanationCheck zero without losing it
shipment.delayMinis zero, so a truthiness condition skips its body. The property still exists.Object.hasOwn(shipment, "delayMin")returns true. Use an own-property check for stored fields; the in operator also includes inherited properties. - 6ExampleRead the entries
const pairs = Object.entries(shipment); const count = Object.keys(shipment).length;There are now four own enumerable string-keyed fields, including checked. Entries gives one
[key, value]pair for each. You can loop withfor (const [key, value] of pairs)to inspect both parts. A direct for...of loop over the plain shipment object would throw because that object is not iterable. - 7ChallengeSeparate existence from value
Suppose
shipment.note = undefined;is added. What doshipment.noteandObject.hasOwn(shipment, "note")return?---reveal---
They return undefined and true. Assignment creates the property even though its value is undefined. Deleting the property would change the existence result to false while a later read would still produce undefined. Value alone cannot distinguish these two situations.
- 8AnalogyTrace two routes to one record
A reference works like a route to a record: copying the route gives another way to reach the same destination. It does not build a second record.
const alias = shipment; alias.checked = false;shipment.checkedis now false too. Both variable names reach one object, soalias === shipmentis true. Matching references explain the shared update; no synchronization step ran between two copies. - 9ExampleAdd data that a shallow copy will share
shipment.packages = [{ massG: 450 }, { massG: 700 }]; const shallow = { ...shipment }; shallow.destination = "Faro"; shallow.packages[0].massG = 500;The new outer record has its own destination, so
shipment.destinationremains Braga. Its packages value is the same array reference as before. Following that shared path changes the first package mass in the original shipment to 500 too. - 10ExampleMake a supported deep copy
const deep = structuredClone(shipment); deep.packages[0].massG = 600;The first original package remains at 500 g because the deep copy has its own nested array and package objects. This record contains only supported data. If it contained a function-valued property, structuredClone would throw DataCloneError instead of silently dropping that method.
- 11ChallengePredict a freeze boundary
Run these statements. Does the nested assignment throw, and what is the second mass afterward?
Object.freeze(shipment); shipment.packages[1].massG = 750;---reveal---
It succeeds and sets the mass to 750. Freezing shipment protects its own properties but not the referenced array or its records. Reassigning
shipment.destinationin strict mode would throw TypeError; this nested assignment reaches a different, unfrozen object. - 12ExampleCalculate the current total
const totalG = shipment.packages.reduce( (sum, pack) => sum + pack.massG, 0 );After the previous updates, the original shipment contains masses 500 and 750 g. Reduce starts at zero, adds 500, then 750, producing 1250. The total is calculated from the current packages. If their masses change later, run the calculation again; this stored number does not recalculate itself.
- 13TipGuard an optional path
const contactName = shipment.contact?.name ?? "Unassigned";There is no contact property in the record, so optional chaining yields undefined and the fallback supplies
"Unassigned". This path is allowed to be absent. Keep required fields visible to validation instead of quietly defaulting all errors. The nullish fallback also preserves an intentionally empty name, unlike a logical OR fallback. - 14ChallengeKeep a valid zero
What do
shipment.delayMin ?? 10andshipment.delayMin || 10return?---reveal---
The first returns 0; the second returns 10. Nullish coalescing replaces only null or undefined, while logical OR replaces every falsy value. This shipment has a recorded zero-minute delay, so treating it as missing would introduce a delay that the stored data never reported.
- 15ChallengeCombine a copy with an optional update
A new record is made with
const next = { ...shipment };, thennext.packages[0].massG = 520;. What happens to shipment's first mass, and is next the same outer object?---reveal---
The original mass becomes 520 because the nested array is shared.
next === shipmentis false because spread created a new outer object. Copying the outer record separates its own assignments but does not make an entire nested path independent. - 16RecapChoose the check for the task
Choose a property by literal or computed key, and distinguish existence from truthiness. Before an update, trace whether its path reaches shared data.
Copy nested levels that must be independent. Recalculate derived totals after their inputs change.
2Keep an action connected to its object
12 steps
Save this lesson to follow every Walkthrough step by step.
- 1IntroductionFollow an action as it moves
- 2ExampleCreate and call an action
- 3ChallengeRead the action without running it
- 4ExampleBorrow the regular function
- 5ChallengeDetach the borrowed method
- 6Key InsightPut an arrow inside the method
- 7ExampleWrap the needed call
- 8ExampleBind a callback once
- 9ChallengeChoose immediate or later execution
- 10TipGive an existing binding priority
- 11ChallengeFollow a bound method through another object
- 12RecapRead the calling form
3Unpack options and build an updated record
14 steps
Save this lesson to follow every Walkthrough step by step.
- 1IntroductionRead compact syntax in small steps
- 2ExampleSelect and rename fields
- 3ExampleEnter a nested pattern
- 4ChallengeRead a renamed nested field
- 5ChallengeKeep null distinct from missing
- 6ExampleUnpack a returned range
- 7ExampleCollect the tail into its own array
- 8ChallengeSwap values before using them
- 9ExampleMerge defaults with overrides
- 10ExampleCopy the changing nested level
- 11ConnectionRemove a field from the new record
- 12ChallengeChoose an independent update
- 13ChallengeConnect entry patterns with spread
- 14RecapIdentify the job before tracing values
4Track counts and unique membership
11 steps
Save this lesson to follow every Walkthrough step by step.
- 1IntroductionChoose what the collection must remember
- 2ExampleCreate counts with Map methods
- 3TipKeep entries separate from properties
- 4ChallengeKeep key types distinct
- 5ExampleReuse an object key
- 6ExampleCollect unique labels
- 7ChallengeDecide whether records are duplicates
- 8ExampleFind the shared and missing labels
- 9Key InsightChange the membership question
- 10ChallengeConnect counts with membership
- 11RecapMatch the structure to the operation
5Write, read, and check JSON data
14 steps
Save this lesson to follow every Walkthrough step by step.
- 1IntroductionPlan both directions of conversion
- 2ExplanationRead text as text
- 3ChallengeFix a syntax problem
- 4ExampleInspect the live settings
- 5ExampleConvert the collection before writing
- 6TipLet the serializer escape strings
- 7ChallengePredict an unsupported array item
- 8ExampleParse and restore membership behavior
- 9ExampleHandle malformed text deliberately
- 10ChallengeCheck a valid but unsuitable value
- 11ExplanationRestore one field with a reviver
- 12ChallengePreserve a Map key distinction
- 13ConnectionKeep cloning and exchange separate
- 14RecapCheck syntax, shape, and representation
Objects and JSON practice questions
Set 1: Read records and follow values10 Practice Questions
Question 1 of 10
What does sensor[field] return?
const sensor = { tempC: 16, field: "room" };
const field = "tempC";
Show answerHide answer
"tempC", to select the property holding 16. Dot access in sensor.field would instead read the literal field property and return "room".Question 2 of 10
What does Object.hasOwn(record, "enabled") return?
const record = { enabled: false };
Show answerHide answer
Question 3 of 10
What are record.region and record.key?
const key = "region";
const record = { [key]: "east" };
record[key] = "west";
Show answerHide answer
"west". No literal key property is created, so reading record.key returns undefined.Question 4 of 10
What is meter.value after this call?
const meter = {
value: 7,
add() { this.value += 3; }
};
meter.add();
Show answerHide answer
Question 5 of 10
What does right.read() return?
const left = { name: "Ivo", read() { return this.name; } };
const right = { name: "Nia", read: left.read };
Show answerHide answer
right, so this is right and the returned name is "Nia". The function was first written in left, but that location does not bind its receiver. Returning "Ivo" would confuse the original storage location with the object used in the current call.Question 6 of 10
What are title and score after destructuring?
const { name: title, score = 8 } = { name: "Reef", score: null };
Show answerHide answer
name into the local variable title. Its default for score is used only for undefined, so the explicit null remains null. Replacing null with 8 would apply the broader nullish-default rule to destructuring, which has a narrower undefined-only trigger.Question 7 of 10
What value does third receive?
const [, , third] = [4, 9, 12, 18];
Show answerHide answer
Question 8 of 10
What are m.size and m.get(2)?
const m = new Map([[2, "number"], ["2", "text"]]);
Show answerHide answer
"2" occupy two separate entries. Looking up numeric 2 returns "number". A size of 1 would be the result of treating both keys as the same string, which is how those names behave as ordinary object properties.Question 9 of 10
Show answerHide answer
Question 10 of 10
What text is returned?
JSON.stringify({
note: undefined,
values: [undefined, NaN],
ok: false
});
Show answerHide answer
note as null would incorrectly use the array rule for an object property; deleting array positions would change their structure.Set 2: Apply patterns and preserve receivers10 Practice Questions
Save this lesson to see every answer and practice every set.
Question 1 of 10
What happens when this text is parsed?
JSON.parse('{"mode":"fast","mode":"slow"}');
Show answerHide answer
Save this lesson to see this answer.
Question 2 of 10
What does trip.host?.name ?? "Guest" return?
const trip = { host: null };
Show answerHide answer
Save this lesson to see this answer.
Question 3 of 10
What are the final values of a.level and a === b?
const a = { level: 2 };
const b = a;
b.level = 5;
Show answerHide answer
Save this lesson to see this answer.
Question 4 of 10
What are source.mode and source.layout.width afterward?
const source = { mode: "day", layout: { width: 40 } };
const copy = { ...source };
copy.mode = "night";
copy.layout.width = 60;
Show answerHide answer
Save this lesson to see this answer.
Question 5 of 10
What function can be passed to map so each call updates tally?
const tally = { n: 0, tick() { return ++this.n; } };
const result = ["x", "y"].map(/* callback */);
Show answerHide answer
Save this lesson to see this answer.
Question 6 of 10
This code runs at module top level. What happens when card.read() is called?
const card = { title: "Basin", read: () => this.title };
Show answerHide answer
Save this lesson to see this answer.
Question 7 of 10
After next.panel.width = 90, what are next.mode and base.panel.width?
const base = { mode: "wide", panel: { width: 50 } };
const next = { mode: "small", ...base, panel: { ...base.panel } };
Show answerHide answer
Save this lesson to see this answer.
Question 8 of 10
What value does size receive?
const { size = 6 } = {};
Show answerHide answer
Save this lesson to see this answer.
Question 9 of 10
What array does this expression produce?
[...new Set(["red", "blue", "red", "green", "blue"])];
Show answerHide answer
Save this lesson to see this answer.
Question 10 of 10
What is the final value of loaded?
const loaded = JSON.parse('{"count":4,"tag":"a"}', (key, value) => {
if (key === "count") return value * 2;
});
Show answerHide answer
Save this lesson to see this answer.
Set 3: Transform collections and recover data10 Practice Questions
Save this lesson to see every answer and practice every set.
Question 1 of 10
What object does the expression create?
Object.fromEntries(
Object.entries({ north: 3, south: 5 })
.map(([key, value]) => [key, value + 2])
);
Show answerHide answer
Save this lesson to see this answer.
Question 2 of 10
What does options.limit ?? 12 return?
const options = { limit: 0 };
Show answerHide answer
Save this lesson to see this answer.
Question 3 of 10
What must add return for basket.add("pear").add("fig") to use the same basket twice?
const basket = {
items: [],
add(item) { this.items.push(item); /* return here */ }
};
Show answerHide answer
Save this lesson to see this answer.
Question 4 of 10
What array is returned in this module?
function scale(n) { return this.factor * n; }
const twice = scale.bind({ factor: 2 });
const obj = { factor: 9, twice };
[obj.twice(3), twice.call({ factor: 5 }, 4)];
Show answerHide answer
Save this lesson to see this answer.
Question 5 of 10
What is returned?
function combine(first, ...others) {
return [first, others.length];
}
combine(...[7, 3, 8, 2]);
Show answerHide answer
Save this lesson to see this answer.
Question 6 of 10
What are items[0].done, next[1] === items[1], and next === items?
const items = [{ id: 4, done: false }, { id: 9, done: false }];
const next = items.map(item =>
item.id === 4 ? { ...item, done: true } : item
);
Show answerHide answer
Save this lesson to see this answer.
Question 7 of 10
Why is lookup.get({ code: 8 }) undefined?
const key = { code: 8 };
const lookup = new Map([[key, "ready"]]);
Show answerHide answer
Save this lesson to see this answer.
Question 8 of 10
What are the sizes of a.difference(b) and a.symmetricDifference(b)?
const a = new Set([2, 4, 6]);
const b = new Set([4, 8]);
Show answerHide answer
Save this lesson to see this answer.
Question 9 of 10
What text does this produce?
JSON.stringify({ label: "Arc", count: 3 }, ["label"]);
Show answerHide answer
Save this lesson to see this answer.
Question 10 of 10
This module runs without a reviver. What are typeof loaded.due and loaded === original?
const original = { due: new Date("2026-07-12T00:00:00.000Z") };
const loaded = JSON.parse(JSON.stringify(original));
Show answerHide answer
Save this lesson to see this answer.
Set 4: Diagnose updates and calls10 Practice Questions
Save this lesson to see every answer and practice every set.
Question 1 of 10
What are base.inner.n, clone.inner.n, and copy.inner.n at the end?
const base = { inner: { n: 2 } };
const copy = { ...base };
const clone = structuredClone(base);
Object.freeze(base);
copy.inner.n = 7;
clone.inner.n = 9;
Show answerHide answer
Save this lesson to see this answer.
Question 2 of 10
What array does Object.keys(data) return?
const data = { z: 1, "10": 2, "02": 3, "2": 4, a: 5 };
Show answerHide answer
Save this lesson to see this answer.
Question 3 of 10
What is totalG, and does changing heavy.massG also change load.items[1].massG?
const load = { items: [
{ massG: 120, qty: 2 },
{ massG: 300, qty: 1 }
] };
const heavy = load.items.find(x => x.massG > 200);
const totalG = load.items.reduce((s, x) => s + x.massG * x.qty, 0);
heavy.massG = 350;
Show answerHide answer
Save this lesson to see this answer.
Question 4 of 10
What does b.labels() return in this module?
const a = {
prefix: "A",
names: ["oak"],
labels() { return this.names.map(n => this.prefix + n); }
};
const b = { prefix: "B", names: ["elm"], labels: a.labels };
Show answerHide answer
Save this lesson to see this answer.
Question 5 of 10
What is typeof box.open?
const box = { open() { return "opened"; } };
Show answerHide answer
Save this lesson to see this answer.
Question 6 of 10
What happens when read() and read({ prefs: null }) are called separately?
function read({ prefs: { theme = "light" } = {} } = {}) {
return theme;
}
Show answerHide answer
Save this lesson to see this answer.
Question 7 of 10
What is tail?
const [head, ...tail] = [6, 8, 11];
Show answerHide answer
Save this lesson to see this answer.
Question 8 of 10
What are m.size and [...m.keys()]?
const m = new Map([["p", 1], ["q", 2]]);
m.set("p", 4);
m["r"] = 5;
m.delete("q");
m.set("q", 6);
Show answerHide answer
Save this lesson to see this answer.
Question 9 of 10
JSON.parse('{"depthM":+7}') fail?Show answerHide answer
Save this lesson to see this answer.
Question 10 of 10
What are result.count and result.total?
const result = JSON.parse('{"count":"6","total":12}',
(key, value) => key === "count" ? Number(value) : value
);
result.total += result.count;
Show answerHide answer
Save this lesson to see this answer.
Set 5: Combine data choices and conversion rules10 Practice Questions
Save this lesson to see every answer and practice every set.
Question 1 of 10
What are a, b, and rest?
let a = 3, b = 8;
[a, b] = [b, a];
const [, ...rest] = [a, b, 13];
Show answerHide answer
Save this lesson to see this answer.
Question 2 of 10
What is Object.hasOwn(record, "old") after these statements?
const record = { old: undefined };
delete record.old;
Show answerHide answer
Save this lesson to see this answer.
Question 3 of 10
What are a === b, a.left === a.right, and b.left === b.right?
const shared = { n: 1 };
const a = { left: shared, right: shared };
const b = JSON.parse(JSON.stringify(a));
Show answerHide answer
Save this lesson to see this answer.
Question 4 of 10
What does saved() return?
const maker = {
n: 5,
make() { return () => this.n; }
};
const other = { n: 8, make: maker.make };
const saved = other.make().bind({ n: 12 });
Show answerHide answer
Save this lesson to see this answer.
Question 5 of 10
What does fixed.apply({ offset: 30 }, [4]) return?
function add(a, b) { return this.offset + a + b; }
const fixed = add.bind({ offset: 2 }, 7);
Show answerHide answer
Save this lesson to see this answer.
Question 6 of 10
After publicCopy.profile.name = "New", what is source.profile.name, and does source still own secret?
const source = { secret: "s", profile: { name: "Old" } };
const { secret, ...publicCopy } = source;
Show answerHide answer
Save this lesson to see this answer.
Question 7 of 10
What is the final value of counts.get("moss")?
const counts = new Map();
for (const word of ["moss", "fern", "moss", "moss"]) {
counts.set(word, (counts.get(word) ?? 0) + 1);
}
Show answerHide answer
Save this lesson to see this answer.
Question 8 of 10
What are records.size and ids.size?
const x = { id: 7 };
const records = new Set([x, x, { id: 7 }]);
const ids = new Set([...records].map(r => r.id));
Show answerHide answer
Save this lesson to see this answer.
Question 9 of 10
What is typeof JSON.parse(text)?
const text = JSON.stringify('{"x":3}');
Show answerHide answer
Save this lesson to see this answer.
Question 10 of 10
"3" with values "n" and "s". What pair of conversions preserves them through JSON?Show answerHide answer
Save this lesson to see this answer.
Stuck on your own question? Homework Help
Key Terms
A
apply
format.apply(reading, ["m", 2]) supplies the same receiver and values as format.call(reading, "m", 2).Array destructuring
const [low, high] = bounds; relies on bounds placing its lower value before its higher value.Array rest element
const [leader, ...others] = names; separates the first name from an array of the remaining names.Array-index key
"3" sorts before "12" in Object.keys; "03" stays among ordinary string keys.Arrow wrapper
() => counter.tick() to map makes each callback invoke tick through the counter object.B
bind
const next = meter.step.bind(meter); creates a callback that keeps the meter receiver.Borrowed method
b.read = a.read lets b.read() use b's data when the function reads through this.Bracket notation
const field = "heightM";, person[field] selects the person's height measurement.C
call
format.call(reading, "m", 2) gives format the reading receiver and two separate arguments.Circular reference
record.self = record makes ordinary JSON.stringify throw a TypeError for that record.Computed property name
const code = "es";, { [code]: "Hola" } creates a property named "es".D
Deep copy
structuredClone, editing a copied nested field leaves the original field unchanged.Derived value
Destructuring default
const { count = 2 } = { count: null };, the explicit null survives the default.Detached method
const read = sensor.read; keeps the function, but read() does not supply sensor as receiver.Difference
active.difference(blocked) keeps active values that do not appear in the blocked Set.Dot notation
profile.name always selects the property named "name", even if a variable called name exists.E
Entry pair
Object.entries({ lake: 5 }) produces [["lake", 5]], ready for a key-value loop.Escape sequence
G
globalThis
I
Immutable update
Indentation argument
Insertion order
Intersection
Iterable
Iterable spread
Math.max(...measurements) supplies each array item as a separate argument to the function.J
JSON
{"ready":true} describes a record, while true alone is also a valid whole JSON text.JSON string
JSON.parse
JSON.parse reads JSON text and returns its represented JavaScript value, throwing a SyntaxError when the text is malformed.JSON.stringify
JSON.stringify serializes supported JavaScript values into JSON text, optionally selecting data and adding readable indentation.JSON.stringify(settings, null, 2) writes a more readable form without changing the represented settings.L
Lexical this
M
Map
"5" remain distinct entries when stored in the same Map.Map entry
stock.set("tea", 4) changes an entry; stock["tea"] = 4 changes an ordinary property.Map.groupBy
Map.groupBy is a newer method that groups iterable items into arrays inside a Map, preserving the type and identity of group keys.Membership test
map.has(key) can find an existing entry even when map.get(key) returns undefined.Method
tracker.advance() runs the tracker's action, while reading tracker.advance returns a function.Method chaining
list.add("a").add("b") works when the first call returns an object with the next add method.Method shorthand
reset() { this.n = 0; } defines an action without a separate colon and function keyword.N
Nested data
report.readings[0].tempC reads a list, its first record, and then a temperature field.Nested pattern
const { owner: { name } } = item; needs an owner object and declares only name.Nullish coalescing
settings.volume ?? 1 keeps an explicit volume of zero, while settings.volume || 1 replaces it.O
Object destructuring
const { city, tempC } = reading; creates two local variables from a weather record.Object identity
id: 9 compare unequal with === despite matching data.Object literal
const reading = { tempC: 22 }; creates a record containing one temperature measurement.Object rest
const { password, ...publicRecord } = user; leaves user intact while excluding password from the copy.Object spread
{ ...base, active: true } copies base and then supplies the final value for active.Object.freeze
Object.freeze prevents additions, deletions, and changes to an object's own properties but does not freeze nested objects.Object.fromEntries
Object.fromEntries creates an object from key-value pairs, making it useful after a transformation of entries or a compatible Map.Object.groupBy
Object.groupBy is a newer method that groups iterable items into arrays under callback-selected keys in an object with no prototype.Optional chaining
record.owner?.name allows a missing owner without trying to read a name from undefined.Options object
{ color, size } lets callers state what each setting means without positional ordering.Own property
Object.hasOwn({}, "toString") is false even though "toString" in {} is true.P
Partial application
multiply.bind(null, 4) makes a function that multiplies its next argument by four.Property
active property in { active: false } exists even though its value is a false boolean.Property existence
count: 0 still has a count property; testing that value as a condition misses it.R
Receiver
account.describe(), account is the receiver that the method uses to read its fields.Reference
const backup = record; gives another way to reach record without copying its fields.Renaming pattern
const { title: label } = page; creates label without declaring a variable called title.Replacer
Rest parameter
function total(...values) can use reduce on values even when the call supplies no arguments.Reviver
JSON.parse callback that transforms values after their children, using its return values to replace or remove them.Round trip
S
Safe integer
Serialization
Set
"ready" twice keeps one value, while adding two separate matching records keeps both.Shallow copy
Shape check
Shorthand property
city, the literal { city } creates the same field as { city: city }.Skipped position
const [, second] = colors; skips the first color and names only the next yielded value.Strict plain call
f and calling f() in a module can fail when it tries to read this.name.structuredClone
Subset
Symmetric difference
SyntaxError
JSON.parse.T
this
toJSON
U
Union
W
WeakMap
WeakSet
About this lesson
Make your own Study Materials
Turn your notes, slides and documents into Study Guides, Practice Questions, Flashcards and 6 more tools.