Object Methods and Spread

Introduction

The Problem with Objects and map

You know how to use map and filter on arrays. But what happens when your data is an object instead of an array?

const product = {
  name: "Wireless Headphones",
  price: 79.99,
  brand: "SoundCo",
  inStock: true,
};

You can't call product.map(...). map only works on arrays.

So how do you process the data inside an object? You convert it to an array first — and JavaScript gives you three built-in tools to do exactly that.

What You'll Learn Today

  • How to get an object's keys as an array with Object.keys()
  • How to get an object's values as an array with Object.values()
  • How to get both at once with Object.entries()
  • How to copy and merge objects safely with the spread operator (...)

Before You Begin

Accept the GitHub Classroom assignment and clone the repository. Follow the README.md to get set up.


Object.keys(): Get the Property Names

Object.keys() returns an array of an object's property names.

const student = {
  name: "Alice",
  grade: 92,
  enrolled: true,
};

const keys = Object.keys(student);
console.log(keys); // ["name", "grade", "enrolled"]

The result is a regular array — so you can use map, filter, or anything else you already know on it.

// How many properties does this object have?
console.log(Object.keys(student).length); // 3

You actually used Object.keys() back in CS Fundamentals 4 to loop over an object's properties. Now you'll use it with map and filter instead.

Practice 1

const product = {
  name: "Laptop",
  price: 899,
  inStock: true,
  brand: "TechCo",
};

Use Object.keys() to get all property names. Then use filter to keep only the keys that are longer than 4 characters.

Hint

Strings have a .length property. "price".length is 5. Chain Object.keys() and filter — the callback receives one key at a time.

Solution
const longKeys = Object.keys(product).filter((key) => key.length > 4);

console.log(longKeys); // ["price", "inStock", "brand"]

Object.values(): Get the Property Values

Object.values() returns an array of just the values — no keys.

const student = {
  name: "Alice",
  grade: 92,
  enrolled: true,
};

const values = Object.values(student);
console.log(values); // ["Alice", 92, true]

This is useful when you only care about the data, not what it's called.

Practice 2

const gradeBreakdown = {
  homework: 85,
  quiz: 62,
  midterm: 91,
  final: 88,
  participation: 95,
};

Use Object.values() and filter to get only the scores that are 80 or above.

Hint

Object.values() gives you an array of just the numbers. From there, filter works exactly like it did in Callbacks and Array Methods — the callback receives one score at a time.

Solution
const passing = Object.values(gradeBreakdown).filter((score) => score >= 80);

console.log(passing); // [85, 91, 88, 95]

Object.entries(): Get Keys and Values Together

Object.entries() returns an array of [key, value] pairs. Each item in the result is a two-element array.

const student = {
  name: "Alice",
  grade: 92,
  enrolled: true,
};

const entries = Object.entries(student);
console.log(entries);
// [
//   ["name", "Alice"],
//   ["grade", 92],
//   ["enrolled", true],
// ]

This is the most powerful of the three because you have access to both the key and the value at the same time. But each item in the result is a two-element array — so before we use entries with map, you need one new tool: array destructuring.

Array Destructuring

When you have an array and want to pull values out into variables, you could do it the manual way:

const pair = ["alice", 95];

const name = pair[0];
const score = pair[1];

Array destructuring does the same thing in one line:

const [name, score] = pair;

console.log(name);  // "alice"
console.log(score); // 95

The square brackets on the left mirror the shape of the array on the right. JavaScript assigns each variable to the matching position.

Destructuring in Callbacks

Object.entries() gives you an array of [key, value] pairs. Callbacks receive each item one at a time — so each item your callback receives is itself a two-element array. You can destructure it right in the callback parameter:

const student = { name: "Alice", grade: 92, enrolled: true };

const lines = Object.entries(student).map(([key, value]) => `${key}: ${value}`);

console.log(lines);
// ["name: Alice", "grade: 92", "enrolled: true"]

([key, value]) is array destructuring in the callback parameter. Instead of receiving the pair as a single array and then pulling items out manually, you name both items right in the function signature. Here's the same operation written both ways:

const student = { name: "Alice", grade: 92, enrolled: true };

// Without destructuring — pull values out manually
const lines = Object.entries(student).map((pair) => {
  const key = pair[0];
  const value = pair[1];
  return `${key}: ${value}`;
});

console.log(lines);
// ["name: Alice", "grade: 92", "enrolled: true"]
const student = { name: "Alice", grade: 92, enrolled: true };

// With destructuring — same result, less noise
const lines = Object.entries(student).map(([key, value]) => `${key}: ${value}`);

console.log(lines);
// ["name: Alice", "grade: 92", "enrolled: true"]

Practice 3

const inventory = {
  apples: 50,
  bananas: 3,
  oranges: 0,
  grapes: 14,
  mangoes: 2,
};

Use Object.entries() and filter to keep only items with fewer than 5 in stock. Then use map to turn each low-stock entry into a warning string like "bananas: only 3 left".

Hint

Chain it: .entries().filter(...).map(...)

You need both the item name and the count to build the message — that's exactly what entries gives you.

Solution
const warnings = Object.entries(inventory)
  .filter(([_, count]) => count < 5)
  .map(([item, count]) => `${item}: only ${count} left`);

console.log(warnings);
// ["bananas: only 3 left", "oranges: only 0 left", "mangoes: only 2 left"]

The _ in filter(([_, count]) => ...) is a convention for "I'm not using this value." The item name is still destructured — we just don't need it until the map step. However, we must have something in that position to 'skip over' the first item in the pair and get to the count.


Checkpoint

Write your answers in handwritten notes ✍️.

  1. What does Object.keys() return?
  2. What does Object.values() return?
  3. What does each item in Object.entries() look like?
  4. You have an object with 5 properties. You call Object.values() then filter. How many items could the result have?
  5. What does ([key, value]) mean in a callback parameter? Why the extra parentheses?

Spread: Copying and Merging Objects

You've already used spread with arrays in Callbacks and Array Methods:

// ✅ Creates a new object with spread
const good = students.map((student) => ({
  ...student,
  passing: student.grade >= 70,
}));

That ...student is the spread operator. It copies all properties from one object into a new one. Here's how it works on its own:

Copying an Object

const student = { name: "Alice", grade: 92 };

// ❌ This doesn't copy — both variables point to the same object
const copy = student;

// ✅ Spread creates a new object with the same properties
const spreadCopy = { ...student };

Why does the first version fail? You learned this in CS Fundamentals 1: objects are stored by reference. copy = student just makes a second label pointing to the same data. Change one, you change both. It's like 2️⃣ remote controls that control the same TV 📺.

Spread creates a genuinely new object.

Adding or Overriding Properties

Spread really shines when you want to create a modified copy without touching the original (avoiding mutation). You can add new properties or override existing ones by listing them after the spread.

const student = { name: "Alice", grade: 92 };

// Add a new property
const withStatus = { ...student, enrolled: true };
console.log(withStatus); // { name: "Alice", grade: 92, enrolled: true }
console.log(student);    // { name: "Alice", grade: 92 } — unchanged ✅

// Override an existing property
const curved = { ...student, grade: 97 };
console.log(curved);  // { name: "Alice", grade: 97 }
console.log(student); // { name: "Alice", grade: 92 } — unchanged ✅

Order matters: properties listed after the spread override ones from it.

Merging Two Objects

const defaults = {
  theme: "light",
  notifications: true,
  itemsPerPage: 10,
};

const userPreferences = {
  theme: "dark",
  itemsPerPage: 25,
};

const settings = { ...defaults, ...userPreferences };
console.log(settings);
// { theme: "dark", notifications: true, itemsPerPage: 25 }

userPreferences is spread after defaults, so its values win on any overlap. notifications stays from defaults because userPreferences doesn't have it.

Practice 4

const profile = { name: "Alex", level: 3, active: true };

Create a new object promoted that has all the same properties, but with level bumped to 4 and a new property badge set to "Gold". Verify that profile is unchanged.

Hint

Spread profile first, then list the properties you want to add or override after it. To verify the original is unchanged, console.log(profile) after.

Solution
const promoted = { ...profile, level: 4, badge: "Gold" };

console.log(promoted); // { name: "Alex", level: 4, active: true, badge: "Gold" }
console.log(profile);  // { name: "Alex", level: 3, active: true } — unchanged ✅

Wrap-Up

Key Takeaways

  • Object.keys(obj) → array of property names
  • Object.values(obj) → array of property values
  • Object.entries(obj) → array of [key, value] pairs
  • All three return regular arrays — you can chain map, filter, and anything else you know
  • Spread { ...obj } creates a new object; it doesn't modify the original
  • Adding properties after the spread overrides any matching keys from the original.