What is the best way to store an ES6 Map in local storage or another location for later use?

let map = new Map([[ 'a', 1 ]]);
map.get('a') // 1

let storageData = JSON.stringify(map);
// Saving in localStorage.

// Later:
let restoredMap = JSON.parse(storageData);
restoredMap.get('a') // TypeError: undefined is not a function

Unfortunately, the line JSON.stringify(map); only returns '{}', causing the map to become an empty object upon restoration.

I came across es6-mapify, which enables conversion between a Map and a plain object. That could be a potential solution, but I was hoping to avoid external dependencies just to persist my map data.

Answer №1

If your keys and values are both capable of being serialized,

localStorage.customMap = JSON.stringify(Array.from(map.entries()));

To reverse the process, you can use:

map = new Map(JSON.parse(localStorage.customMap));

Answer №2

Neat and tidy:

Transforming a Map to an array using the spread operator - JSON.stringify([...myMap])

Answer №3

Typically, serialization is only beneficial if the following condition holds true:

deserialize(serialize(data)).get(key) ≈ data.get(key)

where a ≈ b can be defined as serialize(a) === serialize(b).

This criterion is met when converting an object to JSON:

var obj1 = {foo: [1,2]},
    obj2 = JSON.parse(JSON.stringify(obj1));
obj1.foo; // [1,2]
obj2.foo; // [1,2] :)
JSON.stringify(obj1.foo) === JSON.stringify(obj2.foo); // true :)

This works because properties are restricted to strings, which allows for lossless serialization into strings.

However, ES6 maps permit arbitrary values as keys. This presents a challenge since objects are identified by their reference rather than their data. When serializing objects, these references are lost.

var key = {},
    map1 = new Map([ [1,2], [key,3] ]),
    map2 = new Map(JSON.parse(JSON.stringify([...map1.entries()])));
map1.get(1); // 2
map2.get(1); // 2 :)
map1.get(key); // 3
map2.get(key); // undefined :(

Therefore, I would argue that in most cases it's not feasible to achieve this in a practical manner.

In situations where it could work, it is likely that you can opt for a plain object instead of a map. This approach also offers several benefits:

  • It can be stringified to JSON without losing key information.
  • It is compatible with older browsers.
  • It may result in improved performance.

Answer №4

Expanding on the solution provided by Oriol in their response, there is room for improvement. By leveraging object references for keys only if there is a primary root or entry point into the map, and ensuring that each object key can be traced back to that initial root key, we can enhance the functionality.

If we tweak Oriol's example to utilize Douglas Crockford's JSON.decycle and JSON.retrocycle, we can establish a map that addresses this scenario:

var key = {},
    map1 = new Map([ [1, key], [key, 3] ]),
    map2 = new Map(JSON.parse(JSON.stringify([...map1.entries()])),
    map3 = new Map(JSON.retrocycle(JSON.parse(JSON.stringify(JSON.decycle([...map1.entries()]))));
map1.get(1); // key
map2.get(1); // key
map3.get(1); // key
map1.get(map1.get(1)); // 3 :)
map2.get(map2.get(1)); // undefined :(
map3.get(map3.get(1)); // 3 :)

Through decycling and retrocycling, it becomes feasible to represent cyclic structures and dags within JSON format. This feature comes in handy when establishing relationships between objects without appending extra properties to said objects or when interconnecting primitives with objects bidirectionally using an ES6 Map.

One drawback to note is that utilizing the original key object for the new map is not permissible (map3.get(key); would yield undefined). Nonetheless, maintaining the original key reference while employing a freshly parsed JSON map seems highly improbable in practice.

Answer №5

If you create a custom implementation of the toJSON() function for any class objects, the standard JSON.stringify() will work smoothly!

Maps with Arrays as keys? Maps with other nested Map structures as values? A Map nested inside a regular Object? You can handle it all effortlessly by implementing your own toJSON().

Map.prototype.toJSON = function() {
    return Array.from(this.entries());
};

That's all there is to it! You need to manipulate prototypes in this case. While you could manually add toJSON() to every non-standard object or structure, leveraging JavaScript's capabilities is much more powerful.

DEMO

test = {
    regular: 'object',
    map: new Map([
        [['array', 'key'], 7],
        ['stringKey', new Map([
            ['innerMap', 'supported'],
            ['anotherValue', 8]
        ])]
    ])
};
console.log(JSON.stringify(test));

This code snippet outputs:

{"regular":"object","map":[[["array","key"],7],["stringKey",[["innerMap","supported"],["anotherValue",8]]]]}

However, deserializing back to true Map structures isn't as straightforward. To demonstrate, I'll reconstruct the maps from the resulting string to retrieve a value:

test2 = JSON.parse(JSON.stringify(test));
console.log((new Map((new Map(test2.map)).get('stringKey'))).get('innerMap'));

The output is

"supported"

While this process may seem cumbersome, with some added special handling, you can automate deserialization as well.

Map.prototype.toJSON = function() {
    return ['window.Map', Array.from(this.entries())];
};
Map.fromJSON = function(key, value) {
    return (value instanceof Array && value[0] == 'window.Map') ?
        new Map(value[1]) :
        value
    ;
};

Now the serialized JSON looks like this:

{"regular":"object","test":["window.Map",[[["array","key"],7],["stringKey",["window.Map",[["innerMap","supported"],["anotherValue",8]]]]]]}

And deserializing and using it becomes simple with Map.fromJSON:

test2 = JSON.parse(JSON.stringify(test), Map.fromJSON);
console.log(test2.map.get('stringKey').get('innerMap'));

The output remains the same without creating new instances of Map()

DEMO

Answer №6

If you are working with multi-dimensional Maps, the accepted answer may not be sufficient. It is important to remember that a Map object can contain another Map object as either a key or value.

A more reliable and secure approach to handling this situation would be the following:

function convertMapToArray(m){
  return m.constructor === Map ? [...m].map(([v,k]) => [convertMapToArray(v),convertMapToArray(k)])
                               : m;
}

With this function in place, you can easily make use of it like so:

localStorage.myMap = JSON.stringify(convertMapToArray(myMap))

Answer №7

// save
const mapData = new Map([['a', 1]]);
localStorage.a = JSON.stringify(mapData, replacer);

// load
const newMapData = JSON.parse(localStorage.a, reviver);

// custom replacer and reviver functions
function replacer(key, value) {
  const originalObj = this[key];
  if(originalObj instanceof Map) {
    return {
      type: 'Map',
      data: Array.from(originalObj.entries()), 
    };
  } else {
    return value;
  }
}
function reviver(key, value) {
  if(typeof value === 'object' && value !== null) {
    if (value.type === 'Map') {
      return new Map(value.data);
    }
  }
  return value;
}

I've provided an explanation of the replacer and reviver functions in this Stack Overflow post.

This code is versatile and can handle not only Maps but also nested Maps within arrays or objects during serialization.

Answer №8

Utilizing the localStorage API in JavaScript to store an ES6 Map

issue

"[object Map]" ❌


(() => {
  const map = new Map();
  map.set(1, {id: 1, name: 'eric'});
  
  localStorage.setItem('app', map);
  localStorage.getItem('app');
  
})();

resolution

Using JSON.stringify to serialize the Map object before storing it is the solution. Then, using JSON.parse to deserialize it before accessing the Map object will solve the issue ✅



(() => {
  const map = new Map();
  map.set(1, {id: 1, name: 'eric'});
  
  localStorage.setItem('app', JSON.stringify([...map]));
  const newMap = new Map(JSON.parse(localStorage.getItem('app')));
  
})();

screenshots

https://i.sstatic.net/IrIv7.png

refs

Answer №9

It is important to note that a Map is an ORDERED structure, meaning the first item entered will be the first listed when iterating through it.

Unlike a JavaScript Object, which does not guarantee order, I needed this specific structure (hence using Map). However, it was disappointing to discover that JSON.stringify doesn't work with Maps (though understandable).

To overcome this limitation, I created a 'value_to_json' function which involves parsing everything manually - resorting to JSON.stringify only for basic types.

Subclassing Map with a .toJSON() method didn't prove effective as it expects a value instead of a JSON string and is considered outdated.

Although my use case might be unique in this scenario.

Related:

function value_to_json(value) {
  // Implementation of value_to_json function
}

// Example usage
let m = new Map();
// Add entries to the map
const test = {
  "hello": "ok",
  "map": m
};
console.log(value_to_json(test));

Answer №10

One thing to keep in mind is that attempting to setItem on a large map collection may result in triggering a Quota Exceeded Error. I recently encountered this issue when trying to store a map with 168590 entries in local storage. Unfortunately, the error message popped up. :(

Answer №11

Experience seamless complexity handling with the revolutionary JSBT framework developed by yours truly: https://www.npmjs.com/package/@cheprasov/jsbt

This cutting-edge library excels in encoding and decoding intricate data structures, effortlessly navigating circular dependencies.

const map = new Map([ ['apple', 50], ['banana', 20] ]);

const encodedData = JSBT.encode(map);

const decodedMapStructure = JSBT.decode(encodedData);

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Is there a way to restrict the orientation of a UIWebView using JavaScript or HTML?

Currently, I am working on HTML content for an iPad application. The challenge at hand is locking the UIWebView's content to portrait mode. Despite having already submitted the app to Apple, I have only come across a solution in this question, which s ...

Notify of an Invalid CSRF Token within the Action Buttons Present in the Table

I've encountered a problem with the CSRF token when using a Handlebars each loop. I created a data table using the each loop but I'm unable to submit the delete button. The CSRF token only seems to work for submitting new data (POST) and updating ...

The error message "Vue.JS computed from VueX Store is not recognized" is displaying

Within my Vuex store, I am able to see it in the Vue Devtools tool. However, when attempting to load data into a computed property, an error message stating that it is not defined is displayed. computed: { userData(){ return this.$store.state.use ...

What is the best way to include specific script tags within the <head> and <body> sections when utilizing HtmlWebpackPlugin?

I am currently utilizing HtmlWebpackPlugin in order to create HTML files that include JavaScript. Now, I am interested in inserting custom scripts at various locations within the <head> and <body> tags For instance: How can I, Insert <s ...

What is the significance of receiving an error in Internet Explorer when running the code below?

function checkStepValidity(isValid, dataModel) { if (isValid) { updatedDataModel = mergeObjects(this.updatedDataModel, dataModel); } }, The code above encounters the following error in Internet Explorer / Edge browse ...

The dialogue box fails to appear when accessed from the dropdown menu shadcn

I'm encountering an issue while using nextJS with shadcn. I am attempting to open a dialog from a dropdown menu, but instead of opening, it just closes the dropdown menu. My assumption is that I either need to utilize ref and states or position the al ...

AngularJS, sort through "afoo" excluding "foo"

I am attempting to implement a filter within an ng-repeat Main.HTML <table> <tr ng-repeat="param in MyParam | filter: UnrequestValue"> <td>{{param.Label}}</td> </tr> </table> Main.js MyParam: ...

Reading JSON to reconstruct an object of type HttpCookie

Encountering an issue with deserializing the HttpCookie object from JSON, resulting in the error message: Cannot populate list type System.Web.HttpValueCollection. Path 'Values', line .., position .. To work around this problem, I successfull ...

Trying to access a property that doesn't exist (fetching 'fetchBans')

My goal is to create a command within my Discord bot that can unban a user. However, when I finished writing the code, I encountered an error stating 'Cannot read properties of undefined (reading 'fetchBans'). Here is the section of code cau ...

Tips on showcasing a set number of elements from an array in React

How can I modify my code to display only a specific number of items from the 'portfolioComponents' array, starting at a designated index ('startArrayHere') and incrementing or decrementing that index based on user interaction? I've ...

How to update newly added information through the existing form in ReactJS

Currently, I am working on a CRUD operation in my project. So far, I have successfully implemented the functionalities to add, delete, and display data. However, I am facing challenges with updating existing data. Despite trying various methods, none of th ...

Path expression is not valid as it attempts to access an element

When attempting to modify a single element in an array, I encounter the error message Invalid path expression near attempt to access element. Interestingly, this issue only arises when the array is obtained from --rawInput. For instance: # input: [ 1, 0 ...

implementing select2 and binding an event to it

I have a simple select2 setup where I am passing a common class in the options and trying to trigger a jQuery event on every change or click of an item in the dropdown. Here is my code snippet: <select name="transferfrom" id="transferfrom" data-placeho ...

Having difficulty asserting the dual-function button in both its disabled and enabled states

I have a button that is part of a dual-action setup. This button is disabled until a certain event occurs. Here is the DOM structure while the button is disabled: <div class="doc-buttons"> <a href="#" onclick="actualsize();" id="tip-size" cla ...

When using Node.js, Express.js, and MongoDB, ensure that the route.post() function is provided with proper callback functions instead of

I've been working on setting up a MEAN (MongoDB, Express, Node.js, Angular 6) application. I'm trying to post user signup form data to a MongoDB database, but I keep getting an error message. This is my first time working with the MEAN stack and ...

What is the best way to integrate JavaScript libraries into my NPM build process?

My current website is built using HTML and CSS (with SCSS) and I have been using an NPM build script. Now, I am looking to incorporate some JavaScript libraries into my site, such as lozad. I have already downloaded the necessary dependencies for the libr ...

Posting JSON data to a WebApi Controller in Objective-C

I'm working on implementing a javascript function that sends data to an ASP.NET Web Api controller. Here's the code snippet: var d = { NorthEastPoint: { Latitude: boundingBox.ne.lat, Londitude: boundingBox.ne.lng }, S ...

Simulating an API request using Vue and Jest/Vue test utils

Utilizing Vue for the frontend and Python/Django for the backend, I aim to create tests that verify the functionality of my API calls. However, I am encountering difficulties when attempting to mock out the Axios calls. I suspect there might be an issue w ...

Angular SPA boasting an impressive one million pages

Currently, I am in the process of transitioning my webshop, which contains numerous products, to an Angular SPA application using ngRoute and HTML5 mode. I have come up with a method where I receive some of my routes from the server as a JSON object, but s ...

dynamically adjust table cell width based on number of rows

My HTML structure is as follows: <table border=1 > <tr> <!--this tr has one <td> that needs to be 100% width--> <td></td> </tr> <tr> <!--this tr has two <td> that each need to be ...