JavaScript Object.entries(): Transform Objects to Key-Value Pairs
Master Object.entries() to convert objects into arrays of key-value pairs. Learn practical uses, transformations, and combination with other methods.
Object.entries() returns an array of a given object's own enumerable string-keyed property [key, value] pairs. This powerful method, introduced in ES2017, enables elegant object transformations and iterations that were cumbersome before.
Understanding Object.entries()
Object.entries() transforms an object into an array of arrays, where each inner array contains a key-value pair from the object.
Basic Syntax and Usage
// Syntax
Object.entries(obj);
// Basic example
const person = {
name: 'John',
age: 30,
city: 'New York',
};
const entries = Object.entries(person);
console.log(entries);
// [
// ['name', 'John'],
// ['age', 30],
// ['city', 'New York']
// ]
// Iterating over entries
for (const [key, value] of Object.entries(person)) {
console.log(`${key}: ${value}`);
}
// Output:
// name: John
// age: 30
// city: New York
// Destructuring in forEach
Object.entries(person).forEach(([key, value]) => {
console.log(`Property ${key} has value ${value}`);
});
Property Order
// Numeric keys are sorted
const mixed = {
2: 'two',
1: 'one',
b: 'letter b',
a: 'letter a',
3: 'three',
};
console.log(Object.entries(mixed));
// [
// ['1', 'one'], // Numeric keys first (sorted)
// ['2', 'two'],
// ['3', 'three'],
// ['b', 'letter b'], // String keys in insertion order
// ['a', 'letter a']
// ]
// Symbol properties are ignored
const sym = Symbol('id');
const withSymbol = {
name: 'Test',
[sym]: 123,
value: 456,
};
console.log(Object.entries(withSymbol));
// [['name', 'Test'], ['value', 456]]
// Symbol property not included
Object Transformation Patterns
Converting and Filtering Objects
// Filter object properties
const scores = {
math: 95,
english: 78,
science: 88,
history: 92,
art: 65,
};
// Keep only high scores
const highScores = Object.fromEntries(
Object.entries(scores).filter(([subject, score]) => score >= 85)
);
console.log(highScores);
// { math: 95, science: 88, history: 92 }
// Transform values
const percentages = Object.fromEntries(
Object.entries(scores).map(([subject, score]) => [subject, `${score}%`])
);
console.log(percentages);
// { math: '95%', english: '78%', ... }
// Transform keys
const upperKeys = Object.fromEntries(
Object.entries(scores).map(([key, value]) => [key.toUpperCase(), value])
);
console.log(upperKeys);
// { MATH: 95, ENGLISH: 78, ... }
// Remove null/undefined values
const data = {
name: 'John',
age: null,
email: 'john@example.com',
phone: undefined,
city: 'NYC',
};
const cleaned = Object.fromEntries(
Object.entries(data).filter(([_, value]) => value != null)
);
console.log(cleaned);
// { name: 'John', email: 'john@example.com', city: 'NYC' }
Object Mapping Functions
// Map object values
function mapValues(obj, fn) {
return Object.fromEntries(
Object.entries(obj).map(([key, value]) => [key, fn(value, key, obj)])
);
}
// Map object keys
function mapKeys(obj, fn) {
return Object.fromEntries(
Object.entries(obj).map(([key, value]) => [fn(key, value, obj), value])
);
}
// Examples
const prices = { apple: 1.5, banana: 0.8, orange: 2.0 };
const discounted = mapValues(prices, (price) => price * 0.9);
console.log(discounted);
// { apple: 1.35, banana: 0.72, orange: 1.8 }
const prefixed = mapKeys(prices, (key) => `fruit_${key}`);
console.log(prefixed);
// { fruit_apple: 1.5, fruit_banana: 0.8, fruit_orange: 2.0 }
// Pick specific properties
function pick(obj, keys) {
return Object.fromEntries(
Object.entries(obj).filter(([key]) => keys.includes(key))
);
}
// Omit specific properties
function omit(obj, keys) {
return Object.fromEntries(
Object.entries(obj).filter(([key]) => !keys.includes(key))
);
}
const user = {
id: 1,
name: 'John',
email: 'john@example.com',
password: 'secret',
role: 'admin',
};
console.log(pick(user, ['id', 'name', 'email']));
// { id: 1, name: 'John', email: 'john@example.com' }
console.log(omit(user, ['password']));
// { id: 1, name: 'John', email: 'john@example.com', role: 'admin' }
Working with Nested Objects
Deep Transformations
// Flatten nested object
function flattenObject(obj, prefix = '') {
return Object.entries(obj).reduce((acc, [key, value]) => {
const newKey = prefix ? `${prefix}.${key}` : key;
if (value && typeof value === 'object' && !Array.isArray(value)) {
Object.assign(acc, flattenObject(value, newKey));
} else {
acc[newKey] = value;
}
return acc;
}, {});
}
const nested = {
user: {
name: 'John',
contact: {
email: 'john@example.com',
phone: '123-456',
},
},
settings: {
theme: 'dark',
notifications: true,
},
};
console.log(flattenObject(nested));
// {
// 'user.name': 'John',
// 'user.contact.email': 'john@example.com',
// 'user.contact.phone': '123-456',
// 'settings.theme': 'dark',
// 'settings.notifications': true
// }
// Unflatten object
function unflattenObject(obj) {
const result = {};
Object.entries(obj).forEach(([key, value]) => {
const keys = key.split('.');
let current = result;
keys.forEach((k, i) => {
if (i === keys.length - 1) {
current[k] = value;
} else {
current[k] = current[k] || {};
current = current[k];
}
});
});
return result;
}
// Deep map values
function deepMapValues(obj, fn) {
return Object.fromEntries(
Object.entries(obj).map(([key, value]) => {
if (value && typeof value === 'object' && !Array.isArray(value)) {
return [key, deepMapValues(value, fn)];
}
return [key, fn(value, key)];
})
);
}
const data = {
prices: {
fruits: { apple: 1.5, banana: 0.8 },
vegetables: { carrot: 0.5, lettuce: 1.2 },
},
};
const rounded = deepMapValues(data, (value) =>
typeof value === 'number' ? Math.round(value) : value
);
Practical Applications
Form Data Processing
// Convert form data to object
function formToObject(formElement) {
const formData = new FormData(formElement);
return Object.fromEntries(formData.entries());
}
// Validate form data
function validateFormData(data, rules) {
const errors = {};
Object.entries(rules).forEach(([field, rule]) => {
const value = data[field];
if (rule.required && !value) {
errors[field] = `${field} is required`;
} else if (rule.min && value.length < rule.min) {
errors[field] = `${field} must be at least ${rule.min} characters`;
} else if (rule.pattern && !rule.pattern.test(value)) {
errors[field] = `${field} is invalid`;
}
});
return {
isValid: Object.keys(errors).length === 0,
errors,
};
}
// Example usage
const formData = {
username: 'john',
email: 'invalid-email',
password: '123',
};
const rules = {
username: { required: true, min: 3 },
email: { required: true, pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/ },
password: { required: true, min: 8 },
};
const validation = validateFormData(formData, rules);
console.log(validation);
URL Parameter Handling
// Object to URL params
function objectToParams(obj) {
return Object.entries(obj)
.filter(([_, value]) => value != null && value !== '')
.map(
([key, value]) =>
`${encodeURIComponent(key)}=${encodeURIComponent(value)}`
)
.join('&');
}
// Parse URL params to object
function paramsToObject(queryString) {
return Object.fromEntries(
queryString
.replace(/^\?/, '')
.split('&')
.filter(Boolean)
.map((param) => {
const [key, value] = param.split('=');
return [decodeURIComponent(key), decodeURIComponent(value || '')];
})
);
}
// Examples
const filters = {
category: 'electronics',
minPrice: 100,
maxPrice: 500,
inStock: true,
};
const queryString = objectToParams(filters);
console.log(queryString);
// category=electronics&minPrice=100&maxPrice=500&inStock=true
const parsed = paramsToObject('?category=electronics&minPrice=100');
console.log(parsed);
// { category: 'electronics', minPrice: '100' }
// Advanced URL params with arrays
function objectToParamsAdvanced(obj) {
const params = new URLSearchParams();
Object.entries(obj).forEach(([key, value]) => {
if (Array.isArray(value)) {
value.forEach((v) => params.append(key, v));
} else if (value != null) {
params.set(key, value);
}
});
return params.toString();
}
Configuration Merging
// Deep merge configurations
function mergeConfigs(...configs) {
return configs.reduce((merged, config) => {
Object.entries(config).forEach(([key, value]) => {
if (value && typeof value === 'object' && !Array.isArray(value)) {
merged[key] = mergeConfigs(merged[key] || {}, value);
} else {
merged[key] = value;
}
});
return merged;
}, {});
}
// Environment-specific config
const baseConfig = {
api: {
timeout: 5000,
retries: 3,
},
features: {
analytics: true,
notifications: true,
},
};
const prodConfig = {
api: {
url: 'https://api.production.com',
timeout: 10000,
},
features: {
debug: false,
},
};
const config = mergeConfigs(baseConfig, prodConfig);
console.log(config);
// {
// api: {
// url: 'https://api.production.com',
// timeout: 10000,
// retries: 3
// },
// features: {
// analytics: true,
// notifications: true,
// debug: false
// }
// }
Comparison with Other Methods
Object.entries() vs Object.keys()
const obj = { a: 1, b: 2, c: 3 };
// Object.keys() - only keys
Object.keys(obj).forEach((key) => {
console.log(key, obj[key]); // Need to access obj[key]
});
// Object.entries() - both key and value
Object.entries(obj).forEach(([key, value]) => {
console.log(key, value); // Direct access to both
});
// When you need indices
Object.keys(obj).forEach((key, index) => {
console.log(`${index}: ${key} = ${obj[key]}`);
});
// With entries and index
Object.entries(obj).forEach(([key, value], index) => {
console.log(`${index}: ${key} = ${value}`);
});
Object.entries() vs for...in
// Prototype chain consideration
const parent = { inherited: 'from parent' };
const child = Object.create(parent);
child.own = 'child property';
// for...in includes inherited properties
for (const key in child) {
console.log(key); // 'own', 'inherited'
}
// Object.entries() only own properties
console.log(Object.entries(child)); // [['own', 'child property']]
// Making for...in behave like Object.entries()
for (const key in child) {
if (child.hasOwnProperty(key)) {
console.log(key, child[key]);
}
}
Performance Considerations
Optimization Strategies
// Caching entries for multiple operations
class ObjectProcessor {
constructor(obj) {
this.obj = obj;
this._entries = null;
}
get entries() {
if (!this._entries) {
this._entries = Object.entries(this.obj);
}
return this._entries;
}
filter(predicate) {
return Object.fromEntries(
this.entries.filter(([key, value]) => predicate(value, key))
);
}
map(fn) {
return Object.fromEntries(
this.entries.map(([key, value]) => [key, fn(value, key)])
);
}
invalidate() {
this._entries = null;
}
}
// Batch operations
function batchTransform(objects, transformer) {
return objects.map((obj) =>
Object.fromEntries(
Object.entries(obj).map(([key, value]) => transformer(key, value, obj))
)
);
}
// Early exit with some()
function hasProperty(obj, predicate) {
return Object.entries(obj).some(([key, value]) => predicate(value, key));
}
// Memory-efficient streaming for large objects
function* entriesGenerator(obj) {
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
yield [key, obj[key]];
}
}
}
Advanced Patterns
Type-Safe Object Transformations
// Generic object transformer
function transformObject(obj, options = {}) {
const {
filterKeys = () => true,
filterValues = () => true,
transformKey = (key) => key,
transformValue = (value) => value,
sort = false,
} = options;
let entries = Object.entries(obj)
.filter(([key, value]) => filterKeys(key) && filterValues(value))
.map(([key, value]) => [transformKey(key), transformValue(value)]);
if (sort) {
entries.sort(([a], [b]) => a.localeCompare(b));
}
return Object.fromEntries(entries);
}
// Usage
const original = {
_private: 'hidden',
userName: 'john_doe',
userAge: 30,
isActive: true,
};
const transformed = transformObject(original, {
filterKeys: (key) => !key.startsWith('_'),
transformKey: (key) => key.replace(/([A-Z])/g, '_$1').toLowerCase(),
transformValue: (value) =>
typeof value === 'string' ? value.toUpperCase() : value,
sort: true,
});
console.log(transformed);
// { is_active: true, user_age: 30, user_name: 'JOHN_DOE' }
Object Diffing
// Compare two objects
function diffObjects(obj1, obj2) {
const diff = {
added: {},
removed: {},
changed: {},
};
// Check for added and changed
Object.entries(obj2).forEach(([key, value]) => {
if (!(key in obj1)) {
diff.added[key] = value;
} else if (obj1[key] !== value) {
diff.changed[key] = {
from: obj1[key],
to: value,
};
}
});
// Check for removed
Object.entries(obj1).forEach(([key, value]) => {
if (!(key in obj2)) {
diff.removed[key] = value;
}
});
return diff;
}
// Deep diff
function deepDiff(obj1, obj2, path = '') {
const changes = [];
const allKeys = new Set([
...Object.keys(obj1 || {}),
...Object.keys(obj2 || {}),
]);
allKeys.forEach((key) => {
const currentPath = path ? `${path}.${key}` : key;
const value1 = obj1?.[key];
const value2 = obj2?.[key];
if (value1 === value2) return;
if (
typeof value1 === 'object' &&
typeof value2 === 'object' &&
value1 !== null &&
value2 !== null
) {
changes.push(...deepDiff(value1, value2, currentPath));
} else {
changes.push({
path: currentPath,
from: value1,
to: value2,
});
}
});
return changes;
}
Reactive Object Wrapper
// Create reactive object with change detection
function makeReactive(obj, onChange) {
const handlers = new Map();
const proxy = new Proxy(obj, {
set(target, property, value) {
const oldValue = target[property];
target[property] = value;
if (oldValue !== value) {
onChange({
property,
oldValue,
newValue: value,
entries: Object.entries(target),
});
}
return true;
},
deleteProperty(target, property) {
const oldValue = target[property];
delete target[property];
onChange({
property,
oldValue,
deleted: true,
entries: Object.entries(target),
});
return true;
},
});
return proxy;
}
// Usage
const state = makeReactive({ count: 0, name: 'Test' }, (change) => {
console.log('Object changed:', change);
// Update UI, save to storage, etc.
});
state.count = 1; // Triggers onChange
state.newProp = 'value'; // Triggers onChange
delete state.name; // Triggers onChange
Common Use Cases
Data Validation and Sanitization
// Validate and sanitize object data
class ObjectValidator {
constructor(schema) {
this.schema = schema;
}
validate(obj) {
const errors = [];
const sanitized = {};
Object.entries(this.schema).forEach(([key, rules]) => {
const value = obj[key];
// Check required
if (rules.required && value == null) {
errors.push(`${key} is required`);
return;
}
// Skip optional empty values
if (!rules.required && value == null) {
return;
}
// Type validation
if (rules.type && typeof value !== rules.type) {
errors.push(`${key} must be of type ${rules.type}`);
return;
}
// Custom validation
if (rules.validate && !rules.validate(value)) {
errors.push(`${key} is invalid`);
return;
}
// Sanitize
sanitized[key] = rules.sanitize ? rules.sanitize(value) : value;
});
// Check for extra properties
const extraKeys = Object.keys(obj).filter((key) => !(key in this.schema));
if (extraKeys.length > 0) {
errors.push(`Unknown properties: ${extraKeys.join(', ')}`);
}
return {
isValid: errors.length === 0,
errors,
data: sanitized,
};
}
}
// Usage
const userSchema = {
name: {
type: 'string',
required: true,
sanitize: (value) => value.trim(),
},
email: {
type: 'string',
required: true,
validate: (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value),
sanitize: (value) => value.toLowerCase().trim(),
},
age: {
type: 'number',
required: false,
validate: (value) => value >= 0 && value <= 150,
},
};
const validator = new ObjectValidator(userSchema);
const result = validator.validate({
name: ' John Doe ',
email: 'JOHN@EXAMPLE.COM',
age: 30,
extra: 'field',
});
State Management
// Simple state manager using Object.entries
class StateManager {
constructor(initialState = {}) {
this.state = { ...initialState };
this.listeners = new Set();
this.history = [this.getSnapshot()];
this.historyIndex = 0;
}
getSnapshot() {
return Object.fromEntries(
Object.entries(this.state).map(([key, value]) => [
key,
JSON.parse(JSON.stringify(value)), // Deep clone
])
);
}
setState(updates) {
const oldState = this.getSnapshot();
Object.entries(updates).forEach(([key, value]) => {
if (typeof value === 'function') {
this.state[key] = value(this.state[key]);
} else {
this.state[key] = value;
}
});
const newState = this.getSnapshot();
// Add to history
this.history = this.history.slice(0, this.historyIndex + 1);
this.history.push(newState);
this.historyIndex++;
// Notify listeners
this.notify(oldState, newState);
}
getState() {
return { ...this.state };
}
subscribe(listener) {
this.listeners.add(listener);
return () => this.listeners.delete(listener);
}
notify(oldState, newState) {
const changes = Object.entries(newState).reduce((acc, [key, value]) => {
if (oldState[key] !== value) {
acc[key] = { from: oldState[key], to: value };
}
return acc;
}, {});
this.listeners.forEach((listener) => listener(changes, newState));
}
undo() {
if (this.historyIndex > 0) {
this.historyIndex--;
this.state = this.getSnapshot(this.history[this.historyIndex]);
this.notify(this.history[this.historyIndex + 1], this.state);
}
}
redo() {
if (this.historyIndex < this.history.length - 1) {
this.historyIndex++;
this.state = this.getSnapshot(this.history[this.historyIndex]);
this.notify(this.history[this.historyIndex - 1], this.state);
}
}
}
Best Practices
- Use destructuring for clarity
// Good - clear what's being used
Object.entries(obj).forEach(([key, value]) => {
console.log(`${key}: ${value}`);
});
// Less clear
Object.entries(obj).forEach((entry) => {
console.log(`${entry[0]}: ${entry[1]}`);
});
- Combine with Object.fromEntries() for transformations
// Transform in one pass
const transformed = Object.fromEntries(
Object.entries(original)
.filter(([key, value]) => value != null)
.map(([key, value]) => [key.toUpperCase(), value])
);
- Consider performance for large objects
// For simple iteration, for...in might be faster
for (const key in largeObject) {
if (largeObject.hasOwnProperty(key)) {
// Process key and largeObject[key]
}
}
// But Object.entries() is cleaner for transformations
const filtered = Object.fromEntries(
Object.entries(largeObject).filter(([k, v]) => v > 0)
);
- Remember property enumeration rules
// Only own, enumerable, string-keyed properties
const obj = Object.create(
{ inherited: 'prop' },
{
enumerable: { value: 'yes', enumerable: true },
nonEnumerable: { value: 'no', enumerable: false },
[Symbol('sym')]: { value: 'symbol', enumerable: true },
}
);
console.log(Object.entries(obj)); // [['enumerable', 'yes']]
Conclusion
Object.entries() is a versatile method that bridges the gap between objects and arrays, enabling powerful transformations and iterations. Combined with Object.fromEntries(), it provides a functional approach to object manipulation that's both readable and efficient. Whether you're filtering object properties, transforming data structures, or implementing complex state management, Object.entries() is an essential tool in modern JavaScript development.