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

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 execute a code snippet just once when focusing on a specific field?

<form id="myForm"> <label for="fname">First name:</label><br> <input type="text" id="fname" name="fname"><br> <label for="mname">Middle name:</label> ...

Node.js exec command encountering an uninformative error message and not executing properly

Here is the snippet of code that needs to be executed cp.exec("cc -Wall /tmp/test.c -o /tmp/test", function(e, stdout, stderr) { if (e) { var errorstr = "There was an error during compilation: "+ e.message.toString() ...

`Incorporate concurrent network requests in React for improved performance`

I am looking to fetch time-series data from a rest service, and currently my implementation looks like this async function getTimeSeriesQuery(i) { // Demonstrating the usage of gql appollo.query(getChunkQueryOptions(i)) } var promises = [] for(var i ...

Angular Unit Testing: Executing Multiple expectGET's within a Singular Test

I am facing an issue with a unit test that fails to read the JSON in the second request properly This is my implementation of the Config factory (function() { 'use strict'; angular.module('commercial.factories').factory(&apos ...

The dropdown list in the edit form of jQgrid is displaying an additional quotation mark

In my MVC2 EditStatesController, I have the following code: public JsonResult GetStates() { string statesToReturn = GetStates(); // returns "1: Alabama; 2: Alaska; 3: Arizona; 4: Arkansas" return Json(statesToReturn); } ...

Troubleshooting Laravel: Ajax Call for Deleting Records not Functioning Properly on Button Click Event

I've encountered an issue with deleting a record from an ajax call in a Laravel CRUD application. The click event doesn't seem to be triggered, resulting in the delete function not working as intended. Below is the relevant code snippet. web.php ...

Executing JavaScript's addEventListener() function without specifying the event that triggers it

I am currently working on creating a grid of boxes that can be clicked, sending notifications with their respective IDs to the server using SocketIO. <body> <div class="wrapper"> <div id="1" class="box"></div> <div id="2 ...

What is the process for removing an added message using jQuery after it has been appended by clicking the same button?

https://i.stack.imgur.com/YsmKZ.pnghttps://i.stack.imgur.com/dW2lo.pngI have searched extensively through previously asked questions without success. My goal is to remove the previous appended message when the submit button is clicked again. This functiona ...

Navigating the world of updating problems with Express.js

Currently, I am working on a MEAN stack project and facing challenges with the routing for updating. Saving operations are functioning correctly. In my Angular controller, I have defined the following scope method in which staff represents the object subm ...

Adjustable Scroll Bar

Could anyone help me find a component similar to the resizable scrollbar on Google Finance? Check out this link: http://www.google.com/finance?q=EURUSD&ei=bhM_UKDXF-aIWqAPVNQ You can see it below the chart. If you know where I can find something like ...

JavaScript - Retrieve all properties and methods of an object

Can an object created through a constructor function have its keys listed using the Object.keys() method? Let's consider an example with the following code: function Foo () {} Foo.prototype.bar = 'bar'; Foo.prototype.baz = 'baz'; ...

The replace function fails to recognize Cyrillic characters when combined with the /b flag

Struggling with a persistent issue, I've noticed that my code works perfectly with Latin characters but fails to recognize Cyrillic characters when using jQuery. $('p').each(function() { var $this = $(this); $this.html($this.text().re ...

Steps to activate an event when Windows is loaded

Every time windows load, I want to run $('select[name="order_id"]').change(), but it's not working as expected. After debugging in the browser console, I can see that the script $('select[name="order_id"]').cha ...

Tips for aligning a select and select box when the position of the select has been modified

Recently, I encountered an interesting issue with the default html select element. When you click on the select, its position changes, but the box below it fails to adjust its position accordingly. https://i.stack.imgur.com/SwL3Q.gif Below is a basic cod ...

Exploring the art of reading and writing to a file with JavaScript and NodeJS

I am currently working on creating a function that will scan a file and remove all content below a specific line before adding new lines to the file. So far, I have successfully read the file and identified the target line: function analyze() { lineRe ...

Getting variables from different functions in Python can be achieved by using the return

I am trying to implement a feature where I can fetch a search term from the function getRandomVideo() and then use it in a jQuery statement. For example, if I get "Beethoven" as the search term from the variable searches, I want to use it to retrieve JS ...

invoking a function through prototype

<script> var Nancy = function(){ this.name = 'nancy' } Nancy.prototype.getNancy = function(){ alert(this.name); } Nancy.prototype.getNancy(); function Bob(){ ...

Rendering Next.js app within HTML markup provided as a string

I am facing a challenge with integrating my Next.js app into an external HTML markup. The provided markup structure consists of the following files: header.txt <html> <head> <title>Some title</title> </head> < ...

Not adhering to directive scope when transclusion is used, despite explicit instructions to do so

Trying to use a transcluding directive within another controller, but the inner scope isn't being redefined as expected. Despite trying different methods, I can't seem to figure out what's going wrong. The simplified code looks like this: ...

Is it possible to eliminate any leftover comments from a JSON string using jQuery and Ajax?

Having an issue with a framework that generates invalid JSON String like this: /* { "myobject" : "test"} */ The problem lies in the comments before and after the json string. This is done for security purposes, as it's not recommended to return JSON ...