How to convert Object to Map in JavaScript


In the blogpost, we’ll learn about JavaScript Objects (and its various key types), Maps and then explore various techniques to convert JavaScript objects into maps. We’ll understand how each method handles different types of keys and properties, along with tips and common errors to avoid. We’ll also provide a coding problem to practice these concepts, solidifying your understanding of converting (reaaallly 😎) complex objects into maps.

Lets dive in! 👨‍💻


Refresher: Map, Object, and Object key types

Before diving into the various techniques for converting objects to maps in JavaScript, let’s refresh our understanding of Maps, Objects, and the different types of keys objects can have.

Understanding JavaScript Objects

  • Basics: Objects in JavaScript are collections of properties, where each property is a key-value pair. The keys are always strings or symbols.
  • Properties: Properties can be enumerable or non-enumerable. Enumerable properties show up in for...in loops and methods like Object.keys(), while non-enumerable properties do not.
  • Inheritance: Objects can inherit properties from their prototype, leading to a mix of own properties and inherited properties.

Understanding JavaScript Maps

  • Characteristics: Maps are a collection of key-value pairs where keys can be of any type, not just strings or symbols. Maps maintain the insertion order of elements, which is not guaranteed in objects.
  • Methods and Iteration: Maps come with built-in methods like set(), get(), has(), and delete(), making it easier to work with the data. They also provide iterators which make looping over the map straightforward.

Object Key Types

  • String and Symbol Keys: While object keys are typically strings, they can also be symbols, which are unique and immutable.
  • Non-String Keys: When dealing with non-string keys in objects, remember that these keys are converted to strings, which can lead to unexpected behavior. Maps, on the other hand, can handle any key type.
Object String, Symbol, Non-Enumerable keys (JavaScript)
// JavaScript Object
const obj = {
  stringKey: "value1",
  [Symbol("symbolKey")]: "value2",
};
Object.defineProperty(obj, "nonEnumerableKey", {
  value: "value3",
  enumerable: false,
});

// JavaScript Map
const map = new Map();
map.set("stringKey", "value1");
map.set(Symbol("symbolKey"), "value2");

Ways to Convert an Object to Map in JavaScript

Lets see various techniques to convert JavaScript Object to Map.

Using Object.entries() and Map() Constructor

  • Object.entries() takes an object as an input and returns an array of its own enumerable string-keyed property [key, value] pairs. This includes properties found directly upon the object. The output format of Object.entries() is perfectly aligned with what the Map constructor expects as input.
  • In following example, we first define an object obj with three key-value pairs. We then use Object.entries(obj) to convert this object into an array of [key, value] pairs. Finally, we pass this array to the Map constructor to create a new Map instance.
Object.entries() & Map() Constructor (JavaScript)
const obj = {
  key1: 'value1',
  key2: 'value2',
  key3: 'value3'
};

const map = new Map(Object.entries(obj));

console.log(map);
// Output: 
// Map(3) {"key1" => "value1", "key2" => "value2", "key3" => "value3"}
  • Use for Simple Objects: This method is ideal for objects with simple (key-value like object), enumerable properties ( those that show up in loops like for...in and are returned by methods like Object.keys() and Object.entries()).
  • Check for Object Structure: Works best with flat objects (key-value like Object). Can work on Nested Objects too, but note that if a property value is itself an object or an array (i.e., a nested structure), Object.entries() does not break down this nested structure into its own key-value pairs. Instead, it treats the entire nested object or array as a single value.
Nested Data structure (JavaScript)
const nestedObject = {
    key1: "value1",
    key2: {
        subKey1: "subValue1",
        subKey2: 456
    },
    key3: [1, 2, 3]
};

const map = new Map(Object.entries(nestedObject));
console.log(map);
// Map(3) {
//   "key1" => "value1",
//   "key2" => { subKey1: "subValue1", subKey2: 456 },
//   "key3" => [1, 2, 3]
// }

// 1️⃣ The second entry is "key2" => { subKey1: "subValue1", subKey2: 456 }. 
// Here, the entire object { subKey1: "subValue1", subKey2: 456 } is treated as a single value associated with the key key2.

// 2️⃣ Similarly, for key3, the entire array [1, 2, 3] is a single value in the map.
  • Handling null and undefined Values: A common mistake is not accounting for null or undefined object properties. If your object might contain such values, you should handle them appropriately before converting. The Map constructor won’t throw an error, but the presence of null or undefined can lead to unexpected behavior in your application logic.
Null, Undefined dont throw error in Object.entries() & Map() (JavaScript)
const obj = {
  key1: 'value1',
  key2: null,
  key3: undefined
};

const map = new Map(Object.entries(obj));

console.log(map);
// Output: 
// Map(3) {"key1" => "value1", "key2" => null, "key3" => undefined}

Using Array.map() with Object.keys()

  • Object.keys(obj) returns an array of a given object’s own enumerable property names, iterated in the same order that a normal loop would.
  • The map() method then creates a new array with the results of calling a provided function on every element in this array.
  • Useful when you need to modify the keys or values (or both) while converting the object to a map.
  • In following example, we first use Object.keys(obj) to get an array of keys ['a', 'b', 'c']. We then use map() to transform each key-value pair. The keys are converted to uppercase using key.toUpperCase(), and the values are multiplied by 2. This transformed array is then passed to the Map constructor.
Using Array.map() with Object.keys() (JavaScript)
const obj = {
  a: 1,
  b: 2,
  c: 3,
};

// Capitalise Key and double the value, and convert to map.
const map = new Map(
  Object.keys(obj).map((key) => [
    key.toUpperCase(),
    obj[key] * 2,
  ]),
);

console.log(map);
// Output: Map(3) {"A" => 2, "B" => 4, "C" => 6}
  • Ignoring Non-Enumerable Properties: A key limitation of using Object.keys() is that it only considers the object’s own enumerable properties. This means that non-enumerable properties, which might be present due to property descriptors or inherited through prototypes, will not be included in the conversion. If an object has a non-enumerable property, it will not appear in the array returned by Object.keys(), and therefore, it will not be included in the final Map. See example below.
Ignoring Non-Enumerable Properties (JavaScript)
const obj = Object.create(
  {},
  {
    a: { value: 1, enumerable: true },
    // b as Non-enumerable property
    b: { value: 2, enumerable: false },
  },
);

const map = new Map(
  Object.keys(obj).map((key) => [key, obj[key]]),
);

console.log(map);
// Output: Map(1) {"a" => 1}
// Note: Property 'b' is not included in the map

Using a for...of Loop with Object.entries()

This technique combines the Object.entries() method with a for...of loop to convert an object into a Map in JavaScript.

  • Object.entries(obj) returns an array of [key, value] pairs of the object’s own enumerable properties.
  • The for...of loop then iterates over these pairs, allowing for greater control and flexibility during the iteration process.
  • Useful when you need to add conditional logic or perform more complex operations during the conversion to a Map.
  • In following example, for...of loop iterates over these pairs, and if the value is not null then it transforms the value to uppercase and then add the key-value pair to the map using map.set().
for…of Loop with Object.entries() (JavaScript)
const obj = {
  key1: "value1",
  key2: "value2",
  key3: "value3",
  key4: null,
};

const map = new Map();

for (const [key, value] of Object.entries(obj)) {
  // Adding conditional logic
  if (value !== null) {
    // Example of a complex operation
    map.set(key, value.toUpperCase());
  }
}

console.log(map);
// Output:
// Map(3) {"key1" => "VALUE1", "key2" => "VALUE2", "key3" => "VALUE3"}

Note: Object.entries() only shows enumerable, self owned properties (not inherited properties).

  • Forgetting to Check for Object’s Own Properties: A common mistake when using this technique is not checking whether the properties belong to the object itself and are not inherited from its prototype chain.
  • In JavaScript, an object can inherit properties from another object. When using Object.entries(), these inherited properties are not included, but when using other methods like a for...in loop, you might unintentionally iterate over inherited properties.
  • In the context of Object.entries() with a for...of loop, this is less of a concern, but it’s a good practice to be aware of the object’s property ownership, especially when adapting or expanding on this basic technique.
  • In following example, Object.entries(obj) only returns the object’s own enumerable property [key, value] pairs. Because Object.entries() ignores the inherited properties, the for...of loop only iterates over the properties that are actually part of obj itself, not those inherited from prototype.
Object.entries()doesn’t show inherited properties (JavaScript)
// Define a prototype with a property
const prototype = {
    inheritedProperty: 'inheritedValue'
};

// Create a new object that inherits from the prototype
const obj = Object.create(prototype);
obj.ownProperty = 'ownValue';

// Iterating both own and inherited properties.
for (const key in obj) {
    console.log(key); 
    // This will log both 'ownProperty' and 'inheritedProperty'
}

// Iterating only own properties.
for (const [key, value] of Object.entries(obj)) {
    console.log(`${key}: ${value}`);
    // This will only log 'ownProperty: ownValue'
}

Could there be simpler way to just use Object’s own property? Yes, Object.hasOwnProperty().

Using a for…in Loop with Object.hasOwnProperty()

The for...in loop iterates over all enumerable properties of an object, including those inherited from the prototype chain. To ensure that only the object’s own properties (and not any inherited properties) are added to the Map, the Object.hasOwnProperty() method can be used.

Object.hasOwnProperty(prop) is a method that checks whether the object has the specified property as its own property (as opposed to inheriting it). Its useful in situations where you don’t want to include inherited properties in your Map.

  • In this example, MyObject is a constructor function with a prototype property inheritedProperty.
  • An instance of MyObject, obj, is created and an additional property ownProperty2 is added to it.
  • When we use the for...in loop to iterate over properties of obj, the hasOwnProperty() check ensures that only ownProperty1 and ownProperty2 are added to the map, and the inheritedProperty from the prototype is excluded.
for…in Loop with Object.hasOwnProperty() (JavaScript)
function MyObject() {
  this.ownProperty1 = "value1";
}

MyObject.prototype.inheritedProperty = "value2";

const obj = new MyObject();
obj.ownProperty2 = "value3";

const map = new Map();

for (const key in obj) {
  if (obj.hasOwnProperty(key)) {
    map.set(key, obj[key]);
  }
}

console.log(map);
// Output:
// Map(2) {"ownProperty1" => "value1", "ownProperty2" => "value3"}

Useful method if Object has inheritance, and need granular control while creating Map using Object’s own properties only.

  • Forgetting to use Object.hasOwnProperty(): If for...in loop directly used, then it will loop on inherited properties too.
  • for...in doesn’t behave Like Object.entries(): Note, unlike Object.entries(), which only considers an object’s own enumerable properties, for...in iterates over all enumerable properties, including those inherited. Not recognizing this difference can lead to incorrect assumptions about the properties being iterated over.
  • See below code example and explanation:
Inherited Props: for…in vs Object.entries() (JavaScript)
// Define a prototype with an inherited property
function PrototypeObject() {
  this.inheritedProperty = "inheritedValue";
}

// Create an object that inherits from PrototypeObject
PrototypeObject.prototype.additionalProperty =
  "additionalValue";
const obj = new PrototypeObject();
obj.ownProperty = "ownValue";

// 1️⃣ Create a Map without using hasOwnProperty
const mapWithoutHasOwnProperty = new Map();
for (const key in obj) {
  mapWithoutHasOwnProperty.set(key, obj[key]);
  // This includes both own and inherited properties
}
console.log(
  "Map without hasOwnProperty:",
  mapWithoutHasOwnProperty,
);
// Output:
// Map without hasOwnProperty: Map(3) {
//   'inheritedProperty' => 'inheritedValue',
//   'ownProperty' => 'ownValue',
//   'additionalProperty' => 'additionalValue'
// }

// 2️⃣ Using Object.entries() for comparison
const mapWithObjectEntries = new Map(Object.entries(obj));
console.log(
  "Map with Object.entries():",
  mapWithObjectEntries,
);
// Output:
// Map with Object.entries(): Map(2) {
//   'inheritedProperty' => 'inheritedValue',
//   'ownProperty' => 'ownValue'
// }

Explanation:

  • In the first part, where we don’t use hasOwnProperty, the map mapWithoutHasOwnProperty ends up containing both the properties defined on obj itself (ownProperty) and those inherited from PrototypeObject (additionalProperty).
  • In the second part, we use Object.entries() to create mapWithObjectEntries. This method only considers the object’s own enumerable properties, so it includes only inheritedProperty and ownProperty, but not additionalProperty which is an inherited property.

Using Object.getOwnPropertyNames() for non enumerable properties

Non-enumerable properties are those that are not listed during standard object property enumeration processes, such as a for...in loop or methods like Object.keys(). They are often used for properties that are meant to be hidden from typical object property iterations.

The method Object.getOwnPropertyNames() returns an array of all properties (both enumerable and non-enumerable) found directly upon a given object. This differs from Object.keys() or Object.entries(), which only return enumerable properties. This technique is essential when you need to work with all properties of an object, including those that are not enumerable.

In following example, myObject has both a visible (enumerable) and a hidden (non-enumerable) property. Object.getOwnPropertyNames(myObject) returns an array of both these property names, which we then use to populate a new Map. This map includes both the enumerable and non-enumerable properties.

Object.getOwnPropertyNames() (JavaScript)
const myObject = {
  visibleProperty: "I am visible",
};

Object.defineProperty(myObject, "hiddenProperty", {
  value: "I am hidden",
  enumerable: false,
});

const map = new Map();

// Using Object.getOwnPropertyNames() to include non-enumerable properties
Object.getOwnPropertyNames(myObject).forEach((prop) => {
  map.set(prop, myObject[prop]);
});

console.log(map);
// Output: Map(2) {"visibleProperty" => "I am visible", "hiddenProperty" => "I am hidden"}
  • Ideal when you need to consider every property of an object, especially in cases like deep cloning, property serialization, or when dealing with objects with hidden properties.
  • But be aware, that non-enumerable properties might be set for specific reasons, such as hiding internal properties that are not meant to be exposed or iterated over.

Using Object.getOwnPropertySymbols() for Symbol Keyed Properties

In JavaScript, symbols are used as unique property keys for objects. These symbol-keyed properties are special because they are not enumerable through standard methods like for...in loops or Object.keys().

To access these symbol properties, Object.getOwnPropertySymbols() is used. This method retrieves an array of all symbol properties found directly on a given object. It’s crucial for handling objects that use symbols as property keys, ensuring that no such properties are overlooked.

  • In following example, obj has two symbol-keyed properties (symbolKey1 and symbolKey2) along with a normal enumerable property (normalKey).
  • The Object.getOwnPropertySymbols(obj) method retrieves an array of the object’s symbol properties.
  • The forEach loop is then used to iterate over these symbol properties, adding each one to the map along with its corresponding value.
Object.getOwnPropertySymbols() for Symbol (JavaScript)
const symbolKey1 = Symbol('key1');
const symbolKey2 = Symbol('key2');

const obj = {
    [symbolKey1]: 'value1',
    [symbolKey2]: 'value2',
    normalKey: 'value3'
};

const map = new Map();

// Retrieving and adding symbol-keyed properties to the map
Object.getOwnPropertySymbols(obj).forEach(symbolKey => {
    map.set(symbolKey, obj[symbolKey]);
});

console.log(map);
// Output: Map(2) {Symbol(key1) => "value1", Symbol(key2) => "value2"}

Using Reflect.ownKeys() for a Complete Property List

  • Reflect.ownKeys() is a method in JavaScript that returns an array of all properties found directly upon a given object.
  • This includes all enumerable and non-enumerable properties as well as symbol properties.
  • Unlike Object.keys() or Object.getOwnPropertyNames(), Reflect.ownKeys() provides the most comprehensive list of an object’s properties.
  • This method is particularly useful for a thorough conversion of an object’s properties to a Map, ensuring that no properties, regardless of their nature, are omitted.
  • In following example, obj includes a normal enumerable property, a symbol property, and a non-enumerable property.
  • Using Reflect.ownKeys(obj), we obtain all these property keys, including the symbol and non-enumerable ones, and then iterate over them to create a new Map.
  • This results in a map that represents every property of obj, regardless of their enumerability or type.
Reflect.ownKeys() (JavaScript)
const symbolKey = Symbol("symbolKey");
const obj = {
  normalKey: "value1",
  [symbolKey]: "value2",
};

Object.defineProperty(obj, "nonEnumerableKey", {
  value: "value3",
  enumerable: false,
});

const map = new Map();

Reflect.ownKeys(obj).forEach((key) => {
  map.set(key, obj[key]);
});

console.log(map);
// Output:
// Map(3) {
//		"normalKey" => "value1",
//		Symbol(symbolKey) => "value2",
// 		"nonEnumerableKey" => "value3"
// }
  • Avoid for Simple Scenarios: While Reflect.ownKeys() is powerful, it’s important not to overuse it, especially in simple scenarios where such detailed property enumeration is not necessary. Overusing this method can lead to unnecessarily complex code and potential performance implications.
  • Misunderstanding Symbol Properties: Symbols are often used for specific purposes, such as representing unique keys that won’t clash with other properties. Misusing symbol properties or not handling them correctly can lead to issues in code logic, especially in property access and manipulation.

Tips & errors while converting Object to Map in JavaScript

Lets summarise key issues which can arise from techniques above:

  • Object.entries() and Map() Constructor: Ideal for simple objects, but be mindful of null or undefined values.
  • Array.map() with Object.keys(): Useful for transforming object properties during conversion, but it doesn’t account for non-enumerable properties.
  • for...of Loop with Object.entries(): Offers more control, especially for adding conditional logic, but remember to check for object’s own properties.
  • for...in Loop with Object.hasOwnProperty(): Ensures only the object’s own properties are processed, filtering out inherited ones.
  • Object.getOwnPropertyNames(): Retrieves all properties, including non-enumerable ones, for a complete overview.
  • Object.getOwnPropertySymbols(): Specifically targets symbol-keyed properties, ensuring they are included in the conversion.
  • Reflect.ownKeys(): Provides the most comprehensive list of properties, including non-enumerable and symbol properties, but should be used judiciously.

Further lets explore some more possible issues below.

Handling Nested Objects

Converting objects with nested structures to maps can be challenging because standard conversion techniques (like Object.entries() or a for...in loop) do not automatically convert nested structures into their own map entries. Instead, these nested objects or arrays are treated as single entities/values. This might not be ideal if you want to maintain the nested structure’s granularity within the map.

Possible solutions:

  • Recursive Techniques: Implement a recursive function to handle nested objects. This function would iterate through each property and, if a property is an object itself, recursively apply the same conversion logic. Find below code sample for the same.
  • Flattening the Structure: Flatten the nested object before conversion. This involves transforming the nested structure into a single-level object with compound keys. However, this might lose the original structure’s hierarchy.
Nested Objects (JavaScript)
const nestedObject = {
  level1: {
    level2: {
      key: "value",
    },
  },
};

// 1️⃣ Using Object.entries and Map constructor will not recurseively convert nested objects to Maps, but will still keep entire value as object and place agains Map key.
console.log(new Map(Object.entries(nestedObject)));
// Output: Map(1) { 'level1' => { level2: { key: 'value' } } }


// 2️⃣ But if want to convert nested Objects also to Maps, then need recursion as follows:
function convertToMap(obj) {
  const map = new Map();
  for (const key in obj) {
    if (typeof obj[key] === "object") {
      // Recursive call for nested objects
      map.set(key, convertToMap(obj[key]));
    } else {
      map.set(key, obj[key]);
    }
  }
  return map;
}

const map = convertToMap(nestedObject);
console.log(map);
// Output: Map(1) {"level1" => Map(1) {"level2" => Map(1) {"key" => "value"}}}

Dealing with Non-String Keys

JavaScript objects typically use strings as keys, but Maps can use almost any value, including objects, for keys. When converting objects to Maps, you might encounter non-string keys, especially when dealing with complex data structures or integrating with other systems.

Possible solutions:

  • Key Transformation: Transform non-string keys into string representations if maintaining the key type is not crucial. This could involve serialization or applying a hashing function.
  • Using Maps for Complex Keys: Utilize Maps directly when dealing with complex keys, as Maps naturally handle non-string keys, including objects and symbols, allowing for more flexible data structures.
Symbol (Non-String) Keys (JavaScript)
const objWithNonStringKeys = {
    [Symbol('symbolKey')]: 'symbolValue',
    [123]: 'numericValue'
};

const map = new Map(Object.entries(objWithNonStringKeys).map(([key, value]) => {
    // Transforming non-string object keys (like symbols) to string
    const transformedKey = typeof key === 'symbol' ? key.toString() : key;
    return [transformedKey, value];
}));

console.log(map);
// Output: Map(2) {"Symbol(symbolKey)" => "symbolValue", "123" => "numericValue"}

🧪Practice Coding Problem: Converting Complex Object to Map

In the spirit of Test Driven Learning ( 😁), lets test our understanding by solving a problem.

You have a complex object representing a library system. This object contains nested structures, symbol-keyed properties, and a mix of enumerable and non-enumerable properties. Your task is to write a function that converts this complex object into a Map, ensuring all types of properties (including nested, symbol-keyed, and non-enumerable) are appropriately converted.

Problem (JavaScript)
function convertLibraryObjectToMap(obj) {
  // > > > 👉 Write code here 👈 < < <
}

const librarySymbol = Symbol('library');

const library = {
    books: {
        'Moby Dick': { author: 'Herman Melville', year: 1851 },
        '1984': { author: 'George Orwell', year: 1949 },
        nestedSection: {
            'The Hobbit': { author: 'J.R.R. Tolkien', year: 1937 }
        }
    },
    [librarySymbol]: 'Main Library',
};

Object.defineProperty(library, 'location', {
    value: '123 Library St.',
    enumerable: false
});

// Driver Code:
const libraryMap = convertLibraryObjectToMap(library);
console.log(libraryMap);
/*
Output should be a Map containing keys like 'books', 'librarySymbol', 'location', and within 'books', another Map for nestedSection.
*/
Please attempt before seeing the Answer:
JavaScript
function convertLibraryObjectToMap(obj) {
    const map = new Map();
    const keys = Reflect.ownKeys(obj); // Get all property keys

    keys.forEach(key => {
        const value = obj[key];
        if (value && typeof value === 'object' && !Array.isArray(value)) {
            // Recursive call for nested objects
            map.set(key, convertLibraryObjectToMap(value));
        } else {
            map.set(key, value);
        }
    });

    return map;
}

const libraryMap = convertLibraryObjectToMap(library);
console.log(libraryMap);

Explanation:

  • Recursive Function Approach: function calls itself when it encounters a nested object. This ensures that even deeply nested structures are correctly converted into Maps at each level.
  • Use of Reflect.ownKeys(): to retrieve all keys of the object, including non-enumerable and symbol properties. Ensures that all types of properties are considered in the conversion, not just the enumerable ones.
  • Iterating Over All Properties: The function iterates over each key retrieved from Reflect.ownKeys(obj). For each key, it checks if the corresponding value is an object and not an array (using typeof value === 'object' && !Array.isArray(value)). This check is important to differentiate between objects (which might need further recursive conversion) and other types of properties.
  • Handling Nested Objects: If the value is a nested object, the function makes a recursive call: convertLibraryObjectToMap(value). This call converts the nested object into a Map. The recursive nature of the function ensures that no matter how deeply an object is nested, it gets converted into a nested Map.
  • Setting Values in the Map: For each key, the corresponding value (or the nested Map if the value is an object) is added to the current Map using map.set(key, value).
  • Returning the Map: Finally, after all keys have been processed, the function returns the Map that represents the complete structure of the original object.

Now you are an expert in converting an Object to a Map in JavaScript, with all intricacies involved.

Keep learning, and keep coding! 🚀👨‍💻

Scroll to Top