JavaScript object to array
Have you ever been in a situation where you have an object that you need to iterate over like an array? There are a few options out there like Object.keys
and Object.values
that we'll cover but you may not be satisfied with where those take you so follow to the end for a robust pattern that I use on almost every project I work on.
Consider the following products
object.
const products = {
/*
Product entries in object, not an array!
*/
};
/* ❌ Uncaught TypeError: products.forEach is not a function... */
products.forEach(product => console.log(product));
You cannot immediately iterate on the items in products
.
You may also be writing a React component where you need to render a list of entries in an object.
const products = {
/*
Still an object ☹️
*/
};
const ProductList = () => {
return (
<ul>
/* ❌ Uncaught TypeError: products.map is not a function... */
{products.map(product => (
<li key={product.key}>
{product.name}
</li>
))}
</ul>
)
}
We need to convert our JavaScript object to an array before we can iterate over its entries with functions like forEach
and map
, but how do we do it?
3 methods to convert a JavaScript object to an array#
There are 3 JavaScript methods available to help us convert an object to an array. To expand on the initial example, here is the complete products
object.
const products = {
free: {
name: "Free",
description: "The free plan is for individuals or teams evaluating our service.",
price: 0.00,
},
starter: {
name: "Starter",
description: "The starter plan is ideal for small teams.",
price: 9.99,
},
premium: {
name: "Starter",
description: "The premium plans is great for large teams.",
price: 39.99,
}
};
I also like to refer to this object structure as a "keyed object" because each top level entry is a key, a unique accessor to a specific entry in the object. We will go into the topic of object keys later, but for now let's go back to the 3 methods to convert the object to an array.
Object.keys#
If you only need the keys of the object, you can use Object.keys
.
const keys = Object.keys(products);
/* ["free", "starter", "premium"] */
Object.values#
If you only need the values of the object, you can use Object.values
.
const values = Object.values(products);
/*
[{
name: "Free",
description: "The free plan is for individuals or teams evaluating our service.",
price: 0.00,
}, {
name: "Starter",
description: "The starter plan is ideal for small teams.",
price: 9.99,
}, {
name: "Starter",
description: "The premium plans is great for large teams.",
price: 39.99,
}]
*/
Object.entries#
If you need both keys and values, you can use Object.entries
.
const entries = Object.entries(products);
/*
[
["free", {
name: "Free",
description: "The free plan is for individuals or teams evaluating our service.",
price: 0.00,
}],
["starter", {
name: "Starter",
description: "The starter plan is ideal for small teams.",
price: 9.99,
}],
["premium", {
name: "Starter",
description: "The premium plans is great for large teams.",
price: 39.99,
}]
]
*/
🤔 Are there better optons?#
If your needs are met with one of the above options, great!
If not, then you most likely discovered the downsides to each of these options.
- 😕
Object.keys
strips out the data, which you most likely need. - 🙁
Object.values
strips out the key, which you also probably need. - ☹️
Object.entries
returns an array of arrays, which can be difficult to reason about.
There is a better option. But to prove that we even need a better option, let's go back to rendering a list of items in React with each of these.
Working with Object.keys
#
const products = {/* Object entries */};
const ProductList = () => {
const productKeys = Object.keys(products);
return (
<ul>
{productKeys.map(productKey => (
<li key={productKey}>
{products[productKey].name}
</li>
))}
</ul>
)
}
This works, but to access the data you want (in this case the product name
), you need to go back to the original products
object and access the product you want with the key as an accessor (products[productKey].name
). This becomes more difficult to read and error prone as you increase the number of item properties to extract and render.
Working with Object.values
#
const products = {/* Object entries */};
const ProductList = () => {
const productValues = Object.values(products);
return (
<ul>
{productValues.map(productValue => (
<li key={/* ??? */}>
{productValue.name}
</li>
))}
</ul>
)
}
Each item only has a name
, description
, and price
. The key
itself does not live in the item, it exists one layer above as the key or accessor for the main products
object. By extracting only the item with Object.values
, we lose access to the original key
and since React requires keys for lists, this is not a great option for rendering our React list and likely isn't a great option in other scenarios as well.
Working with Object.entries
#
const products = {/* Object entries */};
const ProductList = () => {
const productEntries = Object.entries(products);
return (
<ul>
{productEntries.map(productEntry => (
<li key={productEntry[0]}>
{productEntry[1].name}
</li>
))}
</ul>
)
}
As with Object.keys
, this also works but it reads awkwardly. Is it clear what lives in productEntry[0]
compared to productEntry[1]
? Is there anything in productEntry[2]
?
❓ Why not just start with an array?#
At this point, you might be convinced that the root of the problem here is the original object structure, so you might be tempted to just convert it to an array.
You may also not have this option because your data might be coming from an external data source that you do not control.
But assume we did have control, then we may conclude all of these array conversion problems go away if we just declare products
as a JavaScript array in the first place.
const products = [{
key: "free", // Note that I preserved the `key` property by adding it to each item in the array
name: "Free",
description: "The free plan is for individuals or teams evaluating our service.",
price: 0.00,
}, {
key: "starter",
name: "Starter",
description: "The starter plan is ideal for small teams.",
price: 9.99,
}, {
key: "premium",
name: "Starter",
description: "The premium plans is great for large teams.",
price: 39.99,
}];
Now our rendering logic is clean.
const products = [/* An array, yay! 😃 */];
const ProductList = () => {
return (
<ul>
{products.map(product => (
<li key={product.key}>
{product.name}
</li>
))}
</ul>
)
}
One minor, easy-to-miss downside of this approach is that it is possible to duplicate keys in a larger array. For example, if you accidentally added 2 items with the starter
key in the products
object, you would accidentally overwrite and lose one of the entries.
🤔 But how do we get a single item?#
You may have missed this earlier, but when products
was an object, accessing a single item could quickly be done with its key. So while we made it easier to iterate through our data, we made it more difficult to perform a single lookup. Single item lookups like this are often necessary in UI patterns like the Master/Detail pattern.
Before, when products
was an object, a single item lookup looked like this.
const products = {/* Object entries */};
const starter = products.starter;
/* Or */
const starter = products["starter"];
Now that products
is an array, you need to perform a find
.
const products = [/* An array */];
const starter = products.find(p => p.key === "starter");
This is okay in some circumstances, but can be slow for larger lists or if you need to execute many lookups in your application.
The best of both worlds#
We do not need to live with this trade off where the structure of our data is either better for iteration or better for single item lookups. We can make all scenarios performant and easy to work with by making our data accessible as both a keyed object and an array.
Going back to the 3 methods we explored earlier, Object.keys
was the most desirable option, so let's build on that by creating a conversion function that can turn any keyed object into an array but that also allows us to write cleaner code as we achieved when we started with a pure array.
/* 🧙♂️ This is where the magic happens */
function convertToArray(obj) {
return Object.keys(obj).map(key => ({
key,
...obj[key],
}));
}
const products = {/* Object entries */};
const productsArray = convertToArray(products);
const ProductList = () => {
return (
<ul>
{productsArray.map(product => (
<li key={product.key}>
{product.name}
</li>
))}
</ul>
)
};
/*
Result of productsArray:
[{
key: "free",
name: "Free",
description: "The free plan is for individuals or teams evaluating our service.",
price: 0.00,
}, {
key: "starter",
name: "Starter",
description: "The starter plan is ideal for small teams.",
price: 9.99,
}, {
key: "premium",
name: "Starter",
description: "The premium plans is great for large teams.",
price: 39.99,
}];
*/
The result of the convertToArray
function is an array identical to the one we handcrafted earlier, importantly mapping each item's key
into the object itself.
Now we have 2 data structures, sourced from a single object. For iteration, we can use productsArray
and for single item lookups we can use products
(e.g. const starter = products.starter
).
In cases when I am storing static data like this, I typically make both easily accessible in a single file that I can import and reuse anywhere.
/* /lib/products.js */
import convertToArray from './convertToArray';
export const products = { /* Object entries */ };
export const productsArray = convertToArray(products);
Final result#
/* /lib/convertToArray.js */
export default function convertToArray(obj) {
return Object.keys(obj).map(key => ({
key,
...obj[key],
}));
}
/* /lib/products.js */
import convertToArray from './convertToArray';
export const products = {
free: {
name: "Free",
description: "The free plan is for individuals or teams evaluating our service.",
price: 0.00,
},
starter: {
name: "Starter",
description: "The starter plan is ideal for small teams.",
price: 9.99,
},
premium: {
name: "Starter",
description: "The premium plans is great for large teams.",
price: 39.99,
}
};
export const productsArray = convertToArray(products);
/* /components/ProductList.js */
import { productsArray } from '../lib/products';
const ProductList = () => {
return (
<ul>
{productsArray.map(product => (
<li key={product.key}>
{product.name}
</li>
))}
</ul>
)
};
export default ProductList;
The next time you need to convert a JavaScript object to an array, I hope you will consider this pattern.
Looking for help with a development or design project?
Reach out to work with me or other senior-level talent.
Contact me