Object Methods and Utilities
Learn the built-in Object utility methods for inspecting and transforming objects: Object.keys, Object.values, Object.entries, Object.assign, Object.fromEntries, Object.hasOwn, and get a working overview of property descriptors.
Beyond the properties you define yourself, the built-in Object class provides a set of static utility methods for working with any object generically — listing its keys, listing its values, merging objects together, and converting between objects and other data shapes like arrays. These utilities are essential for writing code that works with data whose exact shape isn't known in advance.
Object.keys, Object.values, Object.entries
Object.keys(obj) returns an array containing just the object's own enumerable property names (as strings), in the order they were defined. This is the standard way to loop over an object's properties, since objects themselves are not directly iterable with a for...of loop.
Object.values(obj) returns an array of just the corresponding values, in the same order as Object.keys would list the keys. Object.entries(obj) returns an array of [key, value] pairs — essentially combining both — which is especially useful for looping with for...of and array destructuring, or for converting an object into a Map.
Object.assign
Object.assign(target, ...sources) copies all enumerable own properties from one or more source objects into a target object, and returns that same target object. It's commonly used with an empty object literal as the target, {}, to merge two or more objects without mutating either of the originals: Object.assign({}, obj1, obj2).
When multiple source objects share a property name, the last one specified wins — later sources overwrite earlier ones for the same key. Note that Object.assign performs only a shallow copy: nested objects are still shared by reference between the original and the copy.
Object.fromEntries
Object.fromEntries(iterable) does the reverse of Object.entries — it takes an iterable of [key, value] pairs (an array of pairs, or a Map) and builds a plain object from them. This is especially handy after transforming an object's entries with array methods like map() or filter(), since those methods only work on arrays, not objects directly.
A very common pattern is: Object.fromEntries(Object.entries(obj).filter(...)) — convert the object to an array of pairs, filter or transform that array with familiar array methods, then convert it back into an object.
Object.hasOwn (and hasOwnProperty)
Object.hasOwn(obj, prop) is the modern, recommended way to check whether an object has a given property directly on itself (not inherited from its prototype chain), returning true or false. It replaces the older obj.hasOwnProperty(prop) method, which works similarly but can fail or need extra care for objects that don't inherit from the normal Object prototype.
This distinction between "has this property directly" versus "can access this property" (which includes inherited properties) matters because the in operator (prop in obj) checks the entire prototype chain, while Object.hasOwn checks only the object's own properties.
Property descriptors overview
Every property on an object has, in addition to its value, a set of hidden metadata flags called a property descriptor: writable (can its value be reassigned?), enumerable (does it show up in for...in loops and Object.keys?), and configurable (can it be deleted or have its descriptor changed?). Properties created with normal object literal syntax default to all three being true.
Object.getOwnPropertyDescriptor(obj, prop) lets you inspect these flags, and Object.defineProperty(obj, prop, descriptor) lets you create a property with custom flags — for example, a read-only property with writable: false. This is an advanced technique mostly used inside libraries and frameworks, but it's worth knowing it exists, since it explains why some built-in properties behave differently than ones you create yourself.
Core Object utility methods
Object.keys/values/entries all reflect only the object's own enumerable properties, not inherited ones. Object.assign({}, ...) is a common shallow-merge / shallow-copy pattern; the spread operator {...obj} (covered next lesson) does the same thing with lighter syntax. Object.hasOwn is preferred over the in operator when you specifically want to exclude inherited properties.
const obj = { a: 1, b: 2, c: 3 };
console.log(Object.keys(obj)); // ["a", "b", "c"]
console.log(Object.values(obj)); // [1, 2, 3]
console.log(Object.entries(obj)); // [["a",1], ["b",2], ["c",3]]
const merged = Object.assign({}, obj, { d: 4 });
console.log(merged); // { a: 1, b: 2, c: 3, d: 4 }
const fromPairs = Object.fromEntries([["x", 10], ["y", 20]]);
console.log(fromPairs); // { x: 10, y: 20 }
console.log(Object.hasOwn(obj, "a")); // true
console.log(Object.hasOwn(obj, "z")); // falseCode Examples
Object.entries turns the object into an array of [key, value] pairs, which can then be destructured directly in a for...of loop — a common and readable pattern for iterating over an object's data. Expected result: [ 'alice', 'bob', 'carol' ] [ 90, 85, 92 ] alice: 90 bob: 85 carol: 92 for (const [name, score] of Object.entries(scores)) destructures each [key, value] pair as it loops.
Object.assign merges defaults and userSettings into a fresh empty object, with userSettings' fontSize overriding the default because it was passed later in the argument list. defaults itself is never modified, since it was used only as a source, not as the target. Expected result: { theme: 'light', fontSize: 18, notifications: true } { theme: 'light', fontSize: 14, notifications: true }
Object.assign({}, defaults, userSettings); uses an empty object as the target so neither source object is mutated.
Object.entries converts inventory into pairs, filter() keeps only pairs whose count is greater than 0, and Object.fromEntries converts the filtered pairs back into a plain object — a common recipe for filtering an object by its values. Object.hasOwn correctly reports false for toString, since that method is inherited from Object.prototype, not defined directly on inventory. Expected result: { apples: 12, cherries: 30 } true false
Object.fromEntries(Object.entries(inventory).filter(...)); is the standard convert-filter-convert-back pattern for filtering objects.
const scores = { alice: 90, bob: 85, carol: 92 };
console.log(Object.keys(scores));
console.log(Object.values(scores));
for (const [name, score] of Object.entries(scores)) {
console.log(name + ": " + score);
}const defaults = { theme: "light", fontSize: 14, notifications: true };
const userSettings = { fontSize: 18 };
const finalSettings = Object.assign({}, defaults, userSettings);
console.log(finalSettings);
console.log(defaults); // untouched — Object.assign didn't mutate the sourceconst inventory = { apples: 12, bananas: 0, cherries: 30, dates: 0 };
const inStockOnly = Object.fromEntries(
Object.entries(inventory).filter(([item, count]) => count > 0)
);
console.log(inStockOnly);
console.log(Object.hasOwn(inventory, "apples"));
console.log(Object.hasOwn(inventory, "toString")); // false — inherited, not ownConverting between objects and arrays of pairs
Object.entries and Object.fromEntries are inverses of each other, forming a round trip that lets you use array methods (filter, map, sort) on object data.
{ a: 1, b: 2, c: 3 }
│
│ Object.entries()
▼
[ ["a",1], ["b",2], ["c",3] ]
│
│ .filter() / .map() — normal array methods
▼
[ ["a",1], ["c",3] ] (example: filtered)
│
│ Object.fromEntries()
▼
{ a: 1, c: 3 }Important Rules
Object.keys, Object.values, and Object.entries all operate on an object's own enumerable properties, in the same relative order.
Object.assign(target, ...sources) mutates and returns target; pass {} as the target to avoid mutating any of the source objects.
Object.fromEntries reverses Object.entries, rebuilding a plain object from an array (or other iterable) of [key, value] pairs.
Object.hasOwn(obj, prop) checks only the object's own properties, unlike the in operator, which also checks inherited properties.
Property descriptors (writable, enumerable, configurable) control advanced behavior; normal object-literal properties default to all three being true.
Where this shows up
Object.keys/values/entries are the standard way to render dynamic data whose exact fields aren't known ahead of time, such as displaying an arbitrary settings object as a form. Object.assign (and its modern sibling, the spread operator) is the classic pattern for merging default configuration with user-supplied overrides, used constantly in libraries and frameworks.
const merged = Object.assign(defaults, userSettings); // mutates defaults!
const merged = Object.assign({}, defaults, userSettings); // defaults stays untouched
When the target argument is an existing object you still need (like defaults) rather than a fresh {}, Object.assign mutates it directly — a very easy mistake that silently corrupts data you meant to keep as a reusable default.
[Multiple Choice] What does Object.entries(obj) return? A. An array of the object's property names only B. An array of the object's values only C. An array of [key, value] pairs for each of the object's own enumerable properties D. A new object with the same keys but no values
C. An array of [key, value] pairs for each of the object's own enumerable properties — Object.entries returns an array where each element is a two-item array: [key, value], one for every own enumerable property on the object.
[True / False] Object.assign({}, a, b) will mutate a and b if their properties overlap. A. True B. False
B. False — False. Because the target argument is a freshly created empty object ({}), neither a nor b is mutated — only the new target object receives the merged properties, with b's values winning over a's on any shared keys.
Given const prices = { apple: 1.5, bread: 3, milk: 2.5 }, write code using Object.entries and Object.fromEntries to produce a new object with every price rounded up to the nearest whole number, without modifying prices itself.
const prices = { apple: 1.5, bread: 3, milk: 2.5 }; const roundedPrices = Object.fromEntries( Object.entries(prices).map(([item, price]) => [item, Math.ceil(price)]) ); console.log(roundedPrices); // { apple: 2, bread: 3, milk: 3 } console.log(prices); // unchanged: { apple: 1.5, bread: 3, milk: 2.5 }
Use Object.entries(prices).map(([key, value]) => [key, Math.ceil(value)]) then Object.fromEntries on the result.