Map Over an Object's Values
JavaScript objects have no built-in `mapValues` like Ramda or Underscore, so every codebase eventually grows its own. This snippet covers the canonical `Object.fromEntries` transform, a sibling `mapKeys` for renaming, and a combined version that lets you reshape both keys and values in one pass. Use them to coerce types, format display strings, or normalise API payloads in a few lines.
815 views
11
function mapValues(obj, fn) {
return Object.fromEntries(
Object.entries(obj).map(([k, v]) => [k, fn(v, k)])
);
}
const prices = { apple: 1, banana: 2, cherry: 3 };
console.log(mapValues(prices, (n) => n * 100));
// { apple: 100, banana: 200, cherry: 300 }
const raw = { id: '1', count: '42' };
console.log(mapValues(raw, Number));
// { id: 1, count: 42 }The two-step entries then fromEntries pipeline is the most readable functor-like transform in plain JavaScript. Passing both value and key to the callback (in that order, matching Array#map) lets the caller branch on the key when needed without forcing a separate helper. This runs in O(n) time and allocates two intermediate arrays plus the result; for hot paths with thousands of keys, prefer the manual loop variant in the next accordion. Use this version for one-shot transforms where readability beats raw throughput.
function mapValuesFast(obj, fn) {
const result = {};
for (const key in obj) {
if (Object.hasOwn(obj, key)) {
result[key] = fn(obj[key], key);
}
}
return result;
}
const inventory = { apples: 4, oranges: 7, pears: 0 };
console.log(mapValuesFast(inventory, (n, name) => `${name}: ${n} in stock`));
// { apples: 'apples: 4 in stock', oranges: 'oranges: 7 in stock', pears: 'pears: 0 in stock' }When the same transform fires on every request (think feature-flag rollout or per-row formatters), the manual loop avoids two array allocations and is roughly twice as fast in micro-benchmarks. Object.hasOwn(obj, key) filters out inherited properties safely, replacing the older obj.hasOwnProperty.call(...) dance. The for..in loop is fine here because we re-check ownership on every iteration. Reach for this version inside ETL pipelines and renderer hot loops; stick with the fromEntries form everywhere else.
function mapKeys(obj, fn) {
return Object.fromEntries(
Object.entries(obj).map(([k, v]) => [fn(k, v), v])
);
}
function mapEntries(obj, fn) {
return Object.fromEntries(Object.entries(obj).map(([k, v]) => fn(k, v)));
}
const snake = { user_name: 'Ada', user_email: '[email protected]' };
const toCamel = (k) => k.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
console.log(mapKeys(snake, toCamel));
// { userName: 'Ada', userEmail: '[email protected]' }
// Reshape both keys and values in a single pass
const totals = { apples: 4, pears: 7 };
console.log(mapEntries(totals, (k, v) => [k.toUpperCase(), v * 2]));
// { APPLES: 8, PEARS: 14 }Renaming keys (mapKeys) is the natural sibling of mapValues, and once you have both you usually want a mapEntries that returns a fresh [key, value] tuple per pair. The snake-to-camel example is the most common real-world use, often paired with API responses from Python or Rust backends. Watch for collisions: if fn returns the same key for two distinct inputs, the later one silently wins, which is rarely what you want. When that's a risk, accumulate values into an array per key instead of overwriting.
