JavaScriptIntermediate

JavaScript Object Methods: keys, values, entries, assign, freeze

Use Object.keys, values, entries, assign, freeze and seal to loop over, copy, merge and protect objects in JavaScript, with simple real-life examples.

All JavaScript lessons

What you will learn

In the last lesson you learned how to create objects and read their properties. But real programs need more: How many properties does this object have? Can I loop over it? How do I copy it or merge two objects? How do I stop other code from changing it? JavaScript has a built-in toolbox called Object for exactly this. In this lesson you will learn Object.keys, Object.values, Object.entries, Object.assign, Object.freeze and Object.seal, plus Object.fromEntries, which is a very useful partner for entries.

A note about how they are called

These are not called on your object like student.keys(). You call them on the big Object and pass your object in:

const student = { name: "Riya", age: 20 };

console.log(Object.keys(student));   // ["name", "age"]
// student.keys();                   // TypeError: student.keys is not a function

Object.keys(): get all the keys

It returns an array of the property names (as strings).

const cart = { pen: 10, notebook: 60, bag: 450 };

const names = Object.keys(cart);

console.log(names);          // ["pen", "notebook", "bag"]
console.log(names.length);   // 3

Because the result is a normal array, you can use every array method on it:

Object.keys(cart).forEach((name) => {
  console.log(name);
});
// pen
// notebook
// bag

Count the properties

An object has no .length, but Object.keys gives you a way:

const user = { name: "Riya", age: 20, city: "Delhi" };

console.log(Object.keys(user).length);   // 3

Check if an object is empty

function isEmpty(obj) {
  return Object.keys(obj).length === 0;
}

console.log(isEmpty({}));            // true
console.log(isEmpty({ a: 1 }));      // false

Object.values(): get all the values

It returns an array of the values.

const cart = { pen: 10, notebook: 60, bag: 450 };

const prices = Object.values(cart);

console.log(prices);   // [10, 60, 450]

Now maths becomes easy:

const total = Object.values(cart).reduce((sum, price) => sum + price, 0);
console.log(total);   // 520

const highest = Math.max(...Object.values(cart));
console.log(highest);   // 450

Object.entries(): get key and value together

It returns an array of [key, value] pairs. Each pair is a small array of two items.

const cart = { pen: 10, notebook: 60, bag: 450 };

console.log(Object.entries(cart));
// [["pen", 10], ["notebook", 60], ["bag", 450]]

The best way to use it is with for...of and destructuring (unpacking each pair into two variables, which you will study in detail in a later lesson):

for (const [name, price] of Object.entries(cart)) {
  console.log(`${name} costs ₹${price}`);
}
// pen costs ₹10
// notebook costs ₹60
// bag costs ₹450

Quick comparison

Method Gives you For { a: 1, b: 2 }
Object.keys(obj) Array of keys ["a", "b"]
Object.values(obj) Array of values [1, 2]
Object.entries(obj) Array of [key, value] pairs [["a", 1], ["b", 2]]

All three return only the object’s own, enumerable properties. Inherited ones are left out, which is why many developers prefer them to for...in.

Object.fromEntries(): the reverse of entries

Object.fromEntries turns an array of [key, value] pairs back into an object. Together with entries, it lets you transform an object using array methods:

const prices = { pen: 10, notebook: 60, bag: 450 };

// Give 10% discount on every item
const discounted = Object.fromEntries(
  Object.entries(prices).map(([name, price]) => [name, price * 0.9])
);

console.log(discounted);   // { pen: 9, notebook: 54, bag: 405 }

The pattern is always: entries, then array methods, then fromEntries.

Filtering an object works the same way:

const expensive = Object.fromEntries(
  Object.entries(prices).filter(([name, price]) => price > 50)
);

console.log(expensive);   // { notebook: 60, bag: 450 }

Object.assign(): copy and merge

Object.assign(target, ...sources) copies the properties of the sources into the target and returns the target.

const target = { a: 1 };
const source = { b: 2, c: 3 };

Object.assign(target, source);

console.log(target);   // { a: 1, b: 2, c: 3 }

Notice that the target itself was changed. That is often not what you want, so the usual trick is to use a new empty object as the target.

Make a copy

const original = { name: "Riya", age: 20 };

const copy = Object.assign({}, original);

copy.name = "Karan";

console.log(original.name);   // "Riya"  (unchanged)
console.log(copy.name);       // "Karan"

Merge objects: later ones win

If two sources have the same key, the last one wins.

const defaults = { theme: "light", fontSize: 16, language: "en" };
const userChoice = { theme: "dark", fontSize: 18 };

const settings = Object.assign({}, defaults, userChoice);

console.log(settings);
// { theme: "dark", fontSize: 18, language: "en" }

The user’s choices override the defaults, and anything the user did not choose keeps its default value.

It is a shallow copy

Object.assign copies only the first level. Nested objects are shared, not copied:

const original = {
  name: "Riya",
  address: { city: "Delhi" }
};

const copy = Object.assign({}, original);

copy.address.city = "Mumbai";

console.log(original.address.city);   // "Mumbai"  (changed too!)

copy.address and original.address point to the same inner object. For a fully independent copy, modern JavaScript has structuredClone:

const deepCopy = structuredClone(original);

deepCopy.address.city = "Pune";

console.log(original.address.city);   // "Mumbai"  (safe this time)

Tip: the spread syntax { ...a, ...b } does the same job as Object.assign({}, a, b) and is more popular today. You will learn it in the destructuring, spread and rest lesson. Object.assign is still important to know, because you will see it in a lot of existing code.

Object.freeze(): lock an object completely

Object.freeze(obj) makes the object read-only. You cannot add, change or delete anything.

const config = Object.freeze({
  appName: "MyCodeNest",
  version: 1
});

config.version = 2;          // ignored
config.author = "Riya";      // ignored
delete config.appName;       // ignored

console.log(config);   // { appName: "MyCodeNest", version: 1 }

It fails silently (unless you use strict mode)

In normal code, the forbidden change is silently ignored. No error, no change. In strict mode it throws a TypeError:

function tryToChange() {
  "use strict";
  const frozen = Object.freeze({ level: 1 });
  frozen.level = 2;
}

try {
  tryToChange();
} catch (error) {
  console.log(error.name);   // "TypeError"
}

(Strict mode has its own lesson later. Modules and classes use it automatically.)

Check if it is frozen

const a = Object.freeze({ x: 1 });
const b = { x: 1 };

console.log(Object.isFrozen(a));   // true
console.log(Object.isFrozen(b));   // false

Freeze is shallow too

Only the first level is locked. Inner objects and arrays can still change:

const settings = Object.freeze({
  theme: "dark",
  colors: ["red", "blue"]
});

settings.theme = "light";          // ignored
settings.colors.push("green");     // works! the inner array is not frozen

console.log(settings.theme);       // "dark"
console.log(settings.colors);      // ["red", "blue", "green"]

To freeze deeply, freeze the inner parts as well:

const settings2 = Object.freeze({
  theme: "dark",
  colors: Object.freeze(["red", "blue"])
});

console.log(Object.isFrozen(settings2.colors));   // true

Object.seal(): lock the structure, not the values

Object.seal(obj) is a little softer than freeze:

  • You can change the values of existing properties.
  • You cannot add new properties.
  • You cannot delete properties.
const user = Object.seal({ name: "Riya", age: 20 });

user.age = 21;          // allowed: changing a value
user.city = "Delhi";    // ignored: cannot add
delete user.name;       // ignored: cannot delete

console.log(user);              // { name: "Riya", age: 21 }
console.log(Object.isSealed(user));   // true

freeze vs seal vs a normal object

Action Normal object Object.seal Object.freeze
Read properties Yes Yes Yes
Change a value Yes Yes No
Add a property Yes No No
Delete a property Yes No No
Check function n/a Object.isSealed Object.isFrozen

A frozen object is also sealed. Neither one can be undone: there is no “unfreeze”.

Real-life use cases

1. Show a cart as a table

const cart = { pen: 10, notebook: 60, bag: 450 };

for (const [item, price] of Object.entries(cart)) {
  console.log(item.padEnd(10) + "₹" + price);
}
// pen       ₹10
// notebook  ₹60
// bag       ₹450

2. Calculate a total and an average

const marks = { maths: 82, science: 91, english: 76 };

const values = Object.values(marks);
const total = values.reduce((sum, m) => sum + m, 0);
const average = total / values.length;

console.log(total);                  // 249
console.log(average.toFixed(1));     // "83.0"

3. Find the best subject

const marks = { maths: 82, science: 91, english: 76 };

const [bestSubject, bestMarks] = Object.entries(marks).sort((a, b) => b[1] - a[1])[0];

console.log(bestSubject, bestMarks);   // "science" 91

4. Build a URL query string

const filters = { category: "shoes", size: 9, sort: "price" };

const query = Object.entries(filters)
  .map(([key, value]) => `${key}=${value}`)
  .join("&");

console.log("/products?" + query);   // "/products?category=shoes&size=9&sort=price"

(Later you will meet URLSearchParams, which does this for you.)

5. Default settings plus user options

function createChart(userOptions) {
  const defaults = { width: 400, height: 300, color: "blue" };
  return Object.assign({}, defaults, userOptions);
}

console.log(createChart({ color: "green" }));
// { width: 400, height: 300, color: "green" }

6. Constants that nobody can change

const ROLES = Object.freeze({
  ADMIN: "admin",
  EDITOR: "editor",
  VIEWER: "viewer"
});

ROLES.ADMIN = "hacker";   // ignored

console.log(ROLES.ADMIN);   // "admin"

7. Remove empty fields before sending data

const form = { name: "Riya", phone: "", city: "Delhi", email: "" };

const cleaned = Object.fromEntries(
  Object.entries(form).filter(([key, value]) => value !== "")
);

console.log(cleaned);   // { name: "Riya", city: "Delhi" }

8. Count how many times each item appears

const votes = ["js", "css", "js", "html", "js", "css"];

const count = {};
for (const vote of votes) {
  count[vote] = (count[vote] || 0) + 1;
}

console.log(count);   // { js: 3, css: 2, html: 1 }

const winner = Object.entries(count).sort((a, b) => b[1] - a[1])[0][0];
console.log(winner);  // "js"

9. Fixed form fields with seal

const form = Object.seal({ name: "", email: "" });

form.name = "Riya";            // fine: filling a field
form.phone = "9999999999";     // ignored: no new fields allowed

console.log(form);   // { name: "Riya", email: "" }

Common mistakes

  • Writing obj.keys(). It must be Object.keys(obj).
  • Forgetting that Object.assign changes the first argument. Use Object.assign({}, a, b) to leave a untouched.
  • Thinking assign makes a deep copy. Nested objects are still shared. Use structuredClone for a deep copy.
  • Expecting freeze to work deeply. Inner objects and arrays can still change.
  • Expecting an error from freeze. In normal mode the change is silently ignored. Use strict mode if you want an error.
  • Trying to “unfreeze” an object. It is permanent. Make a new copy instead.
  • Confusing seal and freeze. seal still lets you change existing values.
  • Using Object.keys on null or undefined. It throws a TypeError.
  • Forgetting that keys come back as strings, even if you wrote them as numbers.
  • Using for...of directly on an object. It fails. Loop over Object.entries(obj) instead.

Practice

  1. Create const car = { brand: "Tata", model: "Nexon", year: 2024 }; and print its keys, its values and its entries.
  2. Print how many properties car has.
  3. Write isEmpty(obj) and test it with {} and { a: 1 }.
  4. Create const prices = { tea: 15, coffee: 25, juice: 40 };. Use Object.values and reduce to find the total. Then find the most expensive price with Math.max.
  5. Loop over prices with for...of and Object.entries, and print tea costs ₹15 and so on.
  6. Use Object.fromEntries and map to make a new object where every price is doubled.
  7. Use Object.fromEntries and filter to keep only items that cost less than 30.
  8. Merge { a: 1, b: 2 } and { b: 3, c: 4 } with Object.assign without changing either original. What is the value of b?
  9. Make an object with a nested address. Copy it with Object.assign, change the copy’s city, and check the original. Then repeat with structuredClone. What is different?
  10. Challenge: freeze an object that contains an array. Show that you can still push into the array, then write a small deepFreeze(obj) function that freezes every nested object too.

Recap

  • Call these on Object, passing your object: Object.keys(obj), Object.values(obj), Object.entries(obj).
  • keys gives the property names, values gives the values, and entries gives [key, value] pairs. All three return normal arrays, so map, filter and reduce work on them.
  • Object.fromEntries converts pairs back into an object. The pattern entries, array method, fromEntries transforms or filters objects.
  • Object.assign(target, ...sources) copies and merges properties into the target. Use {} as the target to avoid changing your originals. Later sources win. It is a shallow copy.
  • Object.freeze blocks adding, changing and deleting. Object.seal blocks adding and deleting, but allows changing existing values. Both are shallow and permanent.
  • Blocked changes are silently ignored in normal mode, and throw a TypeError in strict mode.
  • Next you will learn the this keyword: how JavaScript decides what this means in the global scope, in objects, in regular functions and in arrow functions.